feat(cw2): Drone related test

This commit is contained in:
js0ny 2025-11-27 13:56:41 +00:00
parent 4e87584723
commit 88a316c0f0
4 changed files with 606 additions and 3 deletions

1
.gitignore vendored
View file

@ -40,3 +40,4 @@ out/
.direnv/
.envrc
localjson
ilp-cw-api/results.json

View file

@ -28,12 +28,17 @@ public class DroneInfoService {
public static final String servicePointsEndpoint = "service-points";
public static final String restrictedAreasEndpoint = "restricted-areas";
private final RestTemplate restTemplate = new RestTemplate();
private final RestTemplate restTemplate;
/**
* Constructor, handles the base url here.
*/
public DroneInfoService() {
this(new RestTemplate());
}
public DroneInfoService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
String baseUrl = System.getenv("ILP_ENDPOINT");
if (baseUrl == null || baseUrl.isBlank()) {
this.baseUrl =
@ -268,11 +273,12 @@ public class DroneInfoService {
}
public List<Drone> fetchAllDrones() {
System.out.println("fetchAllDrones called");
String dronesEndpoint = "drones";
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
System.out.println("Fetching from URL: " + droneUrl);
Drone[] drones = restTemplate.getForObject(droneUrl, Drone[].class);
assert drones != null;
return Arrays.asList(drones);
return drones == null ? new ArrayList<>() : Arrays.asList(drones);
}
public List<RestrictedArea> fetchRestrictedAreas() {

View file

@ -0,0 +1,392 @@
package io.github.js0ny.ilp_coursework.controller;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
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;
import io.github.js0ny.ilp_coursework.data.request.AttrQueryRequest;
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;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(DroneController.class)
public class DroneControllerTest {
@Autowired
private MockMvc mockMvc;
private ObjectMapper objectMapper;
@MockitoBean
private DroneInfoService droneInfoService;
@MockitoBean
private DroneAttrComparatorService droneAttrComparatorService;
@MockitoBean
private PathFinderService pathFinderService;
@BeforeEach
void setUp() {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
}
@Nested
@DisplayName("GET /dronesWithCooling/{state}")
class GetDronesWithCoolingTest {
final String API_ENDPOINT_BASE = "/api/v1/dronesWithCooling/";
@Test
@DisplayName("true -> 200 OK")
void getDronesWithCooling_shouldReturn200AndArrayOfString_whenStateIsTrue()
throws Exception {
String endpoint = API_ENDPOINT_BASE + "true";
List<String> expected = List.of("1", "5", "8", "9");
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)));
}
@Test
@DisplayName("false -> 200 OK")
void getDronesWithCooling_shouldReturn200AndArrayOfString_whenStateIsFalse()
throws Exception {
String endpoint = API_ENDPOINT_BASE + "false";
List<String> 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));
mock.andExpect(status().isOk());
mock.andExpect(
content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("-> 400 Bad Request")
void getDronesWithCooling_shouldReturn400_whenStateIsInvalid()
throws Exception {
String endpoint = API_ENDPOINT_BASE + "invalid";
mockMvc.perform(
get(endpoint).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
}
@Nested
@DisplayName("GET /droneDetails/{droneId}")
class DroneDetailsTest {
static final String API_ENDPOINT_BASE = "/api/v1/droneDetails/";
@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));
String endpoint = API_ENDPOINT_BASE + "1";
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)));
}
@Test
@DisplayName("-> 404 Not Found")
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))
.andExpect(status().isNotFound());
}
}
@Nested
@DisplayName("GET /queryAsPath/{attrName}/{attrVal}")
class GetQueryAsPathTests {
final String API_ENDPOINT_BASE = "/api/v1/queryAsPath/";
@Test
@DisplayName("capacity = 8 -> 200 OK")
void getQueryAsPath_shouldReturn200AndArrayOfString_whenCapacityIs8()
throws Exception {
String attrName = "capacity";
String attrVal = "8";
List<String> 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))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("heating = true -> 200 OK")
void getQueryAsPath_shouldReturn200AndArrayOfString_whenHeatingIsTrue()
throws Exception {
String attrName = "heating";
String attrVal = "true";
List<String> 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))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("cooling = false -> 200 OK")
void getQueryAsPath_shouldReturn200AndArrayOfString_whenCoolingIsFalse()
throws Exception {
String attrName = "cooling";
String attrVal = "false";
List<String> 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))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("maxMoves = 1000 -> 200 OK")
void getQueryAsPath_shouldReturn200AndArrayOfString_whenMaxMovesIs1000()
throws Exception {
String attrName = "maxMoves";
String attrVal = "1000";
List<String> 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))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("invalid = null -> 200 OK (empty list)")
void getQueryAsPath_shouldReturn200AndEmptyArrayOfString_whenInvalidAttribute()
throws Exception {
String attrName = "invalid";
String attrVal = "null";
List<String> expected = List.of();
when(droneAttrComparatorService.dronesWithAttribute(attrName, attrVal))
.thenReturn(expected);
mockMvc.perform(get(API_ENDPOINT_BASE + attrName + "/" + attrVal)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
}
@Nested
@DisplayName("POST /query")
class PostQueryTests {
final String API_ENDPOINT = "/api/v1/query";
@Test
@DisplayName("three eqs -> 200 OK")
void postQuery_shouldReturn200AndArrayOfString_whenThreeEqualsConditions()
throws Exception {
AttrQueryRequest req1 = new AttrQueryRequest("capacity", "=", "20");
AttrQueryRequest req2 = new AttrQueryRequest("heating", "=", "false");
AttrQueryRequest req3 = new AttrQueryRequest("cooling", "=", "true");
AttrQueryRequest[] requestBody = {req1, req2, req3};
List<String> expected = List.of("8");
when(droneAttrComparatorService.dronesSatisfyingAttributes(any(AttrQueryRequest[].class)))
.thenReturn(expected);
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 {
AttrQueryRequest req1 = new AttrQueryRequest("capacity", ">", "8");
AttrQueryRequest req2 = new AttrQueryRequest("maxMoves", "<", "2000");
AttrQueryRequest[] requestBody = {req1, req2};
List<String> expected = List.of("5", "10");
when(droneAttrComparatorService.dronesSatisfyingAttributes(any(AttrQueryRequest[].class)))
.thenReturn(expected);
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 -contradict -> 200 OK (empty list)")
void postQuery_shouldReturn200AndEmptyArrayOfString_whenContradictoryConditions()
throws Exception {
AttrQueryRequest req1 = new AttrQueryRequest("capacity", ">", "8");
AttrQueryRequest req2 = new AttrQueryRequest("capacity", "<", "8");
AttrQueryRequest[] requestBody = {req1, req2};
List<String> expected = List.of();
when(droneAttrComparatorService.dronesSatisfyingAttributes(any(AttrQueryRequest[].class)))
.thenReturn(expected);
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 {
AttrQueryRequest req1 = new AttrQueryRequest("capacity", ">", "8");
AttrQueryRequest req2 = new AttrQueryRequest("maxMoves", "<", "2000");
AttrQueryRequest req3 = new AttrQueryRequest("cooling", "=", "true");
AttrQueryRequest[] requestBody = {req1, req2, req3};
List<String> expected = List.of("5");
when(droneAttrComparatorService.dronesSatisfyingAttributes(any(AttrQueryRequest[].class)))
.thenReturn(expected);
mockMvc.perform(post(API_ENDPOINT)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
}
@Nested
@DisplayName("POST /queryAvailableDrones")
class PostQueryAvailableDronesTests {
final String API_ENDPOINT = "/api/v1/queryAvailableDrones";
@Test
@DisplayName("Example -> 200 OK")
void postQueryAvailableDrones_shouldReturn200AndArrayOfString_whenExampleRequest()
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);
MedDispatchRecRequest[] requestBody = {record};
List<String> 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)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@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
)
);
List<String> 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})))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
@Test
@DisplayName("Complex -> 200 OK")
void postQueryAvailableDrones_shouldReturn200AndArrayOfString_whenComplexRequest()
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 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);
MedDispatchRecRequest[] requestBody = {record1, record2};
List<String> expected = List.of("2", "7", "9");
when(droneInfoService.dronesMatchesRequirements(any(MedDispatchRecRequest[].class)))
.thenReturn(expected);
mockMvc.perform(post(API_ENDPOINT)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody)))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(expected)));
}
}
}

View file

@ -0,0 +1,204 @@
package io.github.js0ny.ilp_coursework.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.when;
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;
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;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestTemplate;
@ExtendWith(MockitoExtension.class)
public class DroneInfoServiceTest {
@Mock
private RestTemplate restTemplate;
private DroneInfoService droneInfoService;
private final String baseUrl = "http://localhost:8080/";
@BeforeEach
void setUp() {
droneInfoService = new DroneInfoService(restTemplate);
ReflectionTestUtils.setField(droneInfoService, "baseUrl", baseUrl);
}
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))
};
}
@Nested
@DisplayName("dronesWithCooling(boolean) tests")
class DronesWithCoolingTests {
@Test
@DisplayName("Should return drones with cooling")
void dronesWithCooling_shouldReturnDronesWithCooling() {
// Arrange
when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class))
.thenReturn(getMockDrones());
// Act
List<String> result = droneInfoService.dronesWithCooling(true);
// Assert
assertThat(result).containsExactly("1");
}
@Test
@DisplayName("Should return drones without cooling")
void dronesWithCooling_shouldReturnDronesWithoutCooling() {
// Arrange
when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class))
.thenReturn(getMockDrones());
// Act
List<String> result = droneInfoService.dronesWithCooling(false);
// Assert
assertThat(result).containsExactly("2", "3");
}
@Test
@DisplayName("Should return empty list when API returns null")
void dronesWithCooling_shouldReturnEmptyList_whenApiReturnsNull() {
// Arrange
when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class))
.thenReturn(null);
// Act
List<String> result = droneInfoService.dronesWithCooling(true);
// Assert
assertThat(result).isEmpty();
}
}
@Nested
@DisplayName("droneDetail(String) tests")
class DroneDetailTests {
@Test
@DisplayName("Should return correct drone details")
void droneDetail_shouldReturnCorrectDrone() {
// Arrange
when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class))
.thenReturn(getMockDrones());
// Act
Drone result = droneInfoService.droneDetail("2");
// Assert
assertThat(result.id()).isEqualTo("2");
assertThat(result.name()).isEqualTo("Drone 2");
}
@Test
@DisplayName("Should throw exception for non-existent drone")
void droneDetail_shouldThrowException_forNonExistentDrone() {
// Arrange
when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class))
.thenReturn(getMockDrones());
// Act & Assert
assertThatThrownBy(() -> droneInfoService.droneDetail("4"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("drone with that ID cannot be found");
}
}
@Nested
@DisplayName("dronesMatchesRequirements(MedDispatchRecRequest[]) tests")
class DronesMatchesRequirementsTests {
private ServicePointDrones[] getMockServicePointDrones() {
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 };
}
@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 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));
// Act
List<String> result = droneInfoService.dronesMatchesRequirements(new MedDispatchRecRequest[] { record });
// Assert
assertThat(result).containsExactly("1");
}
@Test
@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);
// 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));
// Act
List<String> result = droneInfoService.dronesMatchesRequirements(new MedDispatchRecRequest[] { record });
// Assert
assertThat(result).isEmpty();
}
@Test
@DisplayName("Should return all drones if requirements are null or empty")
void dronesMatchesRequirements_shouldReturnAllDrones_forNullOrEmptyRequirements() {
// Arrange
when(restTemplate.getForObject(URI.create(baseUrl + "drones"), Drone[].class))
.thenReturn(getMockDrones());
// Act
List<String> resultNull = droneInfoService.dronesMatchesRequirements(null);
List<String> resultEmpty = droneInfoService.dronesMatchesRequirements(new MedDispatchRecRequest[0]);
// Assert
assertThat(resultNull).containsExactly("1", "2", "3");
assertThat(resultEmpty).containsExactly("1", "2", "3");
}
}
}