From 8e07cc490ee4b588344fdd25729e9c61e0149830 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Nov 2025 13:53:32 +0000 Subject: [PATCH 01/11] ServiceInventoryManagement API tests for range interval and type validation --- .../api/sim638/ServiceApiControllerTest.java | 70 +++++++++++++++++-- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java index 5f5b3f68..c5befc9a 100644 --- a/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java @@ -7,16 +7,19 @@ import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; + +import java.io.*; import java.security.Principal; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.List; +import java.util.Set; + import org.apache.commons.io.IOUtils; import org.etsi.osl.services.api.BaseIT; import org.etsi.osl.tmf.JsonUtils; +import org.etsi.osl.tmf.common.model.Any; +import org.etsi.osl.tmf.common.model.service.Characteristic; import org.etsi.osl.tmf.common.model.service.ServiceSpecificationRef; import org.etsi.osl.tmf.scm633.model.ServiceSpecification; import org.etsi.osl.tmf.scm633.model.ServiceSpecificationCreate; @@ -24,10 +27,7 @@ import org.etsi.osl.tmf.sim638.api.ServiceApiController; import org.etsi.osl.tmf.sim638.model.Service; import org.etsi.osl.tmf.sim638.model.ServiceCreate; import org.etsi.osl.tmf.sim638.service.ServiceRepoService; -import org.etsi.osl.tmf.so641.model.ServiceOrderCreate; -import org.etsi.osl.tmf.so641.model.ServiceOrderItem; -import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; -import org.etsi.osl.tmf.so641.model.ServiceRestriction; +import org.etsi.osl.tmf.so641.model.*; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -177,6 +177,27 @@ public class ServiceApiControllerTest extends BaseIT { } + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testServiceInvalidRangeIntervalIsBadRequest() throws Exception { + ServiceOrderCreate serviceOrder = createServiceOrderWithCharacteristicValue("9000"); + mvc.perform(MockMvcRequestBuilders.post("/serviceInventory/v4/service") + .with(SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .andExpect(status().isBadRequest()); + } + + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testServiceInvalidTypesIsBadRequest() throws Exception { + ServiceOrderCreate serviceOrder = createServiceOrderWithCharacteristicValue("not an integer"); + mvc.perform(MockMvcRequestBuilders.post("/serviceInventory/v4/service") + .with(SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .andExpect(status().isBadRequest()); + } + + private String createService() throws Exception { int servicesCount = serviceRepoService.findAll().size(); @@ -224,6 +245,41 @@ public class ServiceApiControllerTest extends BaseIT { } + private ServiceOrderCreate createServiceOrderWithCharacteristicValue(String characteristicValue) throws Exception { + File sspec = new File("src/test/resources/testServiceSpecValidRangeInterval.json"); + InputStream in = new FileInputStream(sspec); + String sspectext = IOUtils.toString(in, "UTF-8"); + + ServiceSpecificationCreate sspeccr = JsonUtils.toJsonObj(sspectext, ServiceSpecificationCreate.class); + sspeccr.setName("Spec1"); + ServiceSpecification responsesSpec = createServiceSpec(sspeccr); + + ServiceOrderCreate serviceOrder = new ServiceOrderCreate(); + serviceOrder.setCategory("Test Category"); + serviceOrder.setDescription("A Test Service"); + serviceOrder.setRequestedStartDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); + serviceOrder.setRequestedCompletionDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); + + ServiceOrderItem soi = new ServiceOrderItem(); + serviceOrder.getOrderItem().add(soi); + soi.setState(ServiceOrderStateType.ACKNOWLEDGED); + + ServiceRestriction serviceRestriction = new ServiceRestriction(); + ServiceSpecificationRef aServiceSpecificationRef = new ServiceSpecificationRef(); + aServiceSpecificationRef.setId(responsesSpec.getId()); + aServiceSpecificationRef.setName(responsesSpec.getName()); + + serviceRestriction.setServiceSpecification(aServiceSpecificationRef); + serviceRestriction.setName("aServiceRestriction"); + Characteristic characteristic = new Characteristic(); + characteristic.setName("Port"); + characteristic.setValue(new Any(characteristicValue)); + serviceRestriction.setServiceCharacteristic(Set.of(characteristic)); + soi.setService(serviceRestriction); + return serviceOrder; + } + + private ServiceSpecification createServiceSpec( ServiceSpecificationCreate serviceSpecificationCreate) throws Exception { String response = mvc .perform(MockMvcRequestBuilders.post("/serviceCatalogManagement/v4/serviceSpecification") -- GitLab From 2b27038a4315606458030c4c98a979a16cc87e54 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Nov 2025 14:09:51 +0000 Subject: [PATCH 02/11] ServiceOrderManagement API tests for range interval and type validation --- .../so641/ServiceOrderApiControllerTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java index 21f24294..c94290e5 100644 --- a/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java @@ -18,6 +18,8 @@ import java.util.Set; import org.apache.commons.io.IOUtils; import org.etsi.osl.services.api.BaseIT; import org.etsi.osl.tmf.JsonUtils; +import org.etsi.osl.tmf.common.model.Any; +import org.etsi.osl.tmf.common.model.service.Characteristic; import org.etsi.osl.tmf.common.model.service.Place; import org.etsi.osl.tmf.common.model.service.ServiceSpecificationRef; import org.etsi.osl.tmf.pm632.model.ContactMedium; @@ -303,6 +305,27 @@ public class ServiceOrderApiControllerTest extends BaseIT { } + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testCreateServiceOrderInvalidRangeIntervalIsBadRequest() throws Exception { + ServiceOrderCreate serviceOrder = createServiceOrderWithCharacteristicValue("9000"); + mvc.perform(MockMvcRequestBuilders.post("/serviceOrdering/v4/serviceOrder") + .with( SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .andExpect(status().isBadRequest()); + } + + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testCreateServiceOrderInvalidTypesIsBadRequest() throws Exception { + ServiceOrderCreate serviceOrder = createServiceOrderWithCharacteristicValue("not an integer"); + mvc.perform(MockMvcRequestBuilders.post("/serviceOrdering/v4/serviceOrder") + .with( SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .andExpect(status().isBadRequest()); + } + + private String createServiceOrder() throws Exception { @@ -404,4 +427,39 @@ public class ServiceOrderApiControllerTest extends BaseIT { .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) .andExpect(status().isBadRequest()).andReturn().getResponse().getContentAsString(); } + + + private ServiceOrderCreate createServiceOrderWithCharacteristicValue(String characteristicValue) throws Exception { + File sspec = new File("src/test/resources/testServiceSpecValidRangeInterval.json"); + InputStream in = new FileInputStream(sspec); + String sspectext = IOUtils.toString(in, "UTF-8"); + + ServiceSpecificationCreate sspeccr = JsonUtils.toJsonObj(sspectext, ServiceSpecificationCreate.class); + sspeccr.setName("Spec1"); + ServiceSpecification responsesSpec = createServiceSpec(sspeccr); + + ServiceOrderCreate serviceOrder = new ServiceOrderCreate(); + serviceOrder.setCategory("Test Category"); + serviceOrder.setDescription("A Test Service"); + serviceOrder.setRequestedStartDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); + serviceOrder.setRequestedCompletionDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); + + ServiceOrderItem soi = new ServiceOrderItem(); + serviceOrder.getOrderItem().add(soi); + soi.setState(ServiceOrderStateType.ACKNOWLEDGED); + + ServiceRestriction serviceRestriction = new ServiceRestriction(); + ServiceSpecificationRef aServiceSpecificationRef = new ServiceSpecificationRef(); + aServiceSpecificationRef.setId(responsesSpec.getId()); + aServiceSpecificationRef.setName(responsesSpec.getName()); + + serviceRestriction.setServiceSpecification(aServiceSpecificationRef); + serviceRestriction.setName("aServiceRestriction"); + Characteristic characteristic = new Characteristic(); + characteristic.setName("Port"); + characteristic.setValue(new Any(characteristicValue)); + serviceRestriction.setServiceCharacteristic(Set.of(characteristic)); + soi.setService(serviceRestriction); + return serviceOrder; + } } \ No newline at end of file -- GitLab From 795de27cc665700ebe0146cebe6dfab862048fdf Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Nov 2025 16:30:31 +0000 Subject: [PATCH 03/11] Moved ServiceSpecCharacteristicValue validation methods to own class --- ...rviceSpecCharacteristicValueValidator.java | 87 +++++++++++++++++++ .../util/ServiceSpecificationValidator.java | 84 ++---------------- 2 files changed, 92 insertions(+), 79 deletions(-) create mode 100644 src/main/java/org/etsi/osl/tmf/util/ServiceSpecCharacteristicValueValidator.java diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceSpecCharacteristicValueValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceSpecCharacteristicValueValidator.java new file mode 100644 index 00000000..6df54061 --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceSpecCharacteristicValueValidator.java @@ -0,0 +1,87 @@ +package org.etsi.osl.tmf.util; + +import org.etsi.osl.tmf.common.model.Any; +import org.etsi.osl.tmf.common.model.ERangeInterval; +import org.etsi.osl.tmf.common.model.EValueType; +import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristicValue; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Objects; + +public class ServiceSpecCharacteristicValueValidator { + private final ServiceSpecCharacteristicValue serviceSpecCharacteristicValue; + + public ServiceSpecCharacteristicValueValidator(ServiceSpecCharacteristicValue serviceSpecCharacteristicValue) { + this.serviceSpecCharacteristicValue = serviceSpecCharacteristicValue; + } + + public boolean validateType() { + final String INTEGER_REGEX = "[-+]?\\d+"; + final String FLOAT_REGEX = "[-+]?\\d*([.,]\\d+)?([eE][-+]?\\d+)?"; + final String BOOLEAN_REGEX = "(?i)true|false"; + final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + if (serviceSpecCharacteristicValue.getValueType() == null) { + return true; + } + Any value = serviceSpecCharacteristicValue.getValue(); + if (value == null) { + return true; + } + String stringValue = value.getValue(); + if (stringValue == null || stringValue.isBlank()) { + return true; + } + try { + return switch (EValueType.getEnum(serviceSpecCharacteristicValue.getValueType())) { + case INTEGER, SMALLINT, lONGINT -> stringValue.matches(INTEGER_REGEX); + case FLOAT -> stringValue.matches(FLOAT_REGEX); + case BOOLEAN -> stringValue.matches(BOOLEAN_REGEX) || stringValue.matches(INTEGER_REGEX); + case TIMESTAMP -> { + try { + LocalDateTime.parse(stringValue, TIMESTAMP_FORMATTER); + yield true; + } catch (DateTimeParseException e) { + yield false; + } + } + default -> true; + }; + } catch (IllegalArgumentException e) { + return false; + } + } + + public boolean isWithinRangeInterval() { + if (serviceSpecCharacteristicValue.getRangeInterval() == null) { + return true; + } + if (!Objects.equals(serviceSpecCharacteristicValue.getValueType(), EValueType.INTEGER.getValue()) && + !Objects.equals(serviceSpecCharacteristicValue.getValueType(), EValueType.SMALLINT.getValue()) && + !Objects.equals(serviceSpecCharacteristicValue.getValueType(), EValueType.lONGINT.getValue())) { + return true; + } + Any value = serviceSpecCharacteristicValue.getValue(); + if (value == null) { + return true; + } + String stringValue = value.getValue(); + if (stringValue == null || stringValue.isBlank()) { + return true; + } + int valueFrom = serviceSpecCharacteristicValue.getValueFrom() != null ? serviceSpecCharacteristicValue.getValueFrom() : Integer.MIN_VALUE; + int valueTo = serviceSpecCharacteristicValue.getValueTo() != null ? serviceSpecCharacteristicValue.getValueTo() : Integer.MAX_VALUE; + try { + int intValue = Integer.parseInt(stringValue); + return switch (ERangeInterval.getEnum(serviceSpecCharacteristicValue.getRangeInterval())) { + case OPEN -> intValue > valueFrom && intValue < valueTo; + case CLOSED -> intValue >= valueFrom && intValue <= valueTo; + case CLOSED_BOTTOM -> intValue >= valueFrom && intValue < valueTo; + case CLOSED_TOP -> intValue > valueFrom && intValue <= valueTo; + }; + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceSpecificationValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceSpecificationValidator.java index ef874fef..0c3d2b39 100644 --- a/src/main/java/org/etsi/osl/tmf/util/ServiceSpecificationValidator.java +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceSpecificationValidator.java @@ -1,19 +1,10 @@ package org.etsi.osl.tmf.util; -import org.etsi.osl.tmf.common.model.Any; -import org.etsi.osl.tmf.common.model.ERangeInterval; -import org.etsi.osl.tmf.common.model.EValueType; -import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristicValue; import org.etsi.osl.tmf.scm633.model.ServiceSpecificationUpdate; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.Validator; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.Objects; - @Component public class ServiceSpecificationValidator implements Validator { @@ -31,78 +22,13 @@ public class ServiceSpecificationValidator implements Validator { boolean invalid = update.getServiceSpecCharacteristic().stream() .flatMap(serviceSpecCharacteristic -> serviceSpecCharacteristic.getServiceSpecCharacteristicValue().stream()) - .anyMatch(serviceSpecCharacteristicValue -> - !validateType(serviceSpecCharacteristicValue) || !isWithinRangeInterval(serviceSpecCharacteristicValue)); + .anyMatch(serviceSpecCharacteristicValue -> { + ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = + new ServiceSpecCharacteristicValueValidator(serviceSpecCharacteristicValue); + return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); + }); if (invalid) { errors.reject("invalid.request"); } } - - private boolean validateType(ServiceSpecCharacteristicValue serviceSpecCharacteristicValue) { - final String INTEGER_REGEX = "[-+]?\\d+"; - final String FLOAT_REGEX = "[-+]?\\d*([.,]\\d+)?([eE][-+]?\\d+)?"; - final String BOOLEAN_REGEX = "(?i)true|false"; - final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - if (serviceSpecCharacteristicValue.getValueType() == null) { - return true; - } - Any value = serviceSpecCharacteristicValue.getValue(); - if (value == null) { - return true; - } - String stringValue = value.getValue(); - if (stringValue == null || stringValue.isBlank()) { - return true; - } - try { - return switch (EValueType.getEnum(serviceSpecCharacteristicValue.getValueType())) { - case INTEGER, SMALLINT, lONGINT -> stringValue.matches(INTEGER_REGEX); - case FLOAT -> stringValue.matches(FLOAT_REGEX); - case BOOLEAN -> stringValue.matches(BOOLEAN_REGEX) || stringValue.matches(INTEGER_REGEX); - case TIMESTAMP -> { - try { - LocalDateTime.parse(stringValue, TIMESTAMP_FORMATTER); - yield true; - } catch (DateTimeParseException e) { - yield false; - } - } - default -> true; - }; - } catch (IllegalArgumentException e) { - return false; - } - } - - private boolean isWithinRangeInterval(ServiceSpecCharacteristicValue serviceSpecCharacteristicValue) { - if (serviceSpecCharacteristicValue.getRangeInterval() == null) { - return true; - } - if (!Objects.equals(serviceSpecCharacteristicValue.getValueType(), EValueType.INTEGER.getValue()) && - !Objects.equals(serviceSpecCharacteristicValue.getValueType(), EValueType.SMALLINT.getValue()) && - !Objects.equals(serviceSpecCharacteristicValue.getValueType(), EValueType.lONGINT.getValue())) { - return true; - } - Any value = serviceSpecCharacteristicValue.getValue(); - if (value == null) { - return true; - } - String stringValue = value.getValue(); - if (stringValue == null || stringValue.isBlank()) { - return true; - } - int valueFrom = serviceSpecCharacteristicValue.getValueFrom() != null ? serviceSpecCharacteristicValue.getValueFrom() : Integer.MIN_VALUE; - int valueTo = serviceSpecCharacteristicValue.getValueTo() != null ? serviceSpecCharacteristicValue.getValueTo() : Integer.MAX_VALUE; - try { - int intValue = Integer.parseInt(stringValue); - return switch (ERangeInterval.getEnum(serviceSpecCharacteristicValue.getRangeInterval())) { - case OPEN -> intValue > valueFrom && intValue < valueTo; - case CLOSED -> intValue >= valueFrom && intValue <= valueTo; - case CLOSED_BOTTOM -> intValue >= valueFrom && intValue < valueTo; - case CLOSED_TOP -> intValue > valueFrom && intValue <= valueTo; - }; - } catch (IllegalArgumentException e) { - return false; - } - } } -- GitLab From 1044db62632799166c9b048f6d775a79ff2e1353 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Nov 2025 17:06:47 +0000 Subject: [PATCH 04/11] ServiceInventory Validator and fixed resource path in tests --- .../tmf/sim638/api/ServiceApiController.java | 13 +++++ .../osl/tmf/util/CharacteristicParser.java | 48 +++++++++++++++++++ .../tmf/util/ServiceInventoryValidator.java | 46 ++++++++++++++++++ .../api/sim638/ServiceApiControllerTest.java | 2 +- 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java create mode 100644 src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java diff --git a/src/main/java/org/etsi/osl/tmf/sim638/api/ServiceApiController.java b/src/main/java/org/etsi/osl/tmf/sim638/api/ServiceApiController.java index ac5ee19b..9771d5d4 100644 --- a/src/main/java/org/etsi/osl/tmf/sim638/api/ServiceApiController.java +++ b/src/main/java/org/etsi/osl/tmf/sim638/api/ServiceApiController.java @@ -30,6 +30,8 @@ import org.etsi.osl.tmf.sim638.model.ServiceCreate; import org.etsi.osl.tmf.sim638.model.ServiceUpdate; import org.etsi.osl.tmf.sim638.service.ServiceRepoService; import org.etsi.osl.tmf.util.AddUserAsOwnerToRelatedParties; +import org.etsi.osl.tmf.util.ServiceInventoryValidator; +import org.etsi.osl.tmf.util.ServiceSpecificationValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -40,6 +42,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RequestMapping; import org.etsi.osl.model.nfv.UserRoleType; @@ -61,6 +65,15 @@ public class ServiceApiController implements ServiceApi { @Autowired ServiceRepoService serviceRepoService; + @Autowired + private ServiceInventoryValidator serviceInventoryValidator; + + // Custom validation resulting from ServiceSpecCharacteristicValue range interval and type validation (https://labs.etsi.org/rep/groups/osl/code/-/epics/30) + @InitBinder + protected void initBinder(WebDataBinder binder) { + binder.addValidators(serviceInventoryValidator); + } + @org.springframework.beans.factory.annotation.Autowired public ServiceApiController(ObjectMapper objectMapper, HttpServletRequest request) { this.objectMapper = objectMapper; diff --git a/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java b/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java new file mode 100644 index 00000000..270b529c --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java @@ -0,0 +1,48 @@ +package org.etsi.osl.tmf.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.etsi.osl.tmf.common.model.Any; +import org.etsi.osl.tmf.common.model.service.Characteristic; +import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristicValue; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +public class CharacteristicParser { + private static final ObjectMapper mapper = new ObjectMapper(); + + public Set toSetOfCharacteristicValues(Characteristic characteristic) { + Set result = new HashSet<>(); + Any input = characteristic.getValue(); + if (input == null) { + return result; + } + try { + JsonNode node = mapper.readTree(input.getValue()); + if (node.isArray()) { + Set values = mapper.readValue(input.getValue(), new TypeReference>() {}); + result = values.stream().map(value -> { + ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = new ServiceSpecCharacteristicValue(); + serviceSpecCharacteristicValue.setValue(value); + return serviceSpecCharacteristicValue; + }).collect(Collectors.toSet()); + } else if (node.isObject()) { + ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = new ServiceSpecCharacteristicValue(); + serviceSpecCharacteristicValue.setValue(mapper.treeToValue(node, Any.class)); + result.add(serviceSpecCharacteristicValue); + } else { + ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = new ServiceSpecCharacteristicValue(); + serviceSpecCharacteristicValue.setValue(input); + result.add(serviceSpecCharacteristicValue); + } + } catch (Exception e) { + ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = new ServiceSpecCharacteristicValue(); + serviceSpecCharacteristicValue.setValue(input); + result.add(serviceSpecCharacteristicValue); + } + return result; + } +} diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java new file mode 100644 index 00000000..b42401e8 --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java @@ -0,0 +1,46 @@ +package org.etsi.osl.tmf.util; + +import org.etsi.osl.tmf.common.model.service.Characteristic; +import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristic; +import org.etsi.osl.tmf.scm633.model.ServiceSpecification; +import org.etsi.osl.tmf.scm633.reposervices.ServiceSpecificationRepoService; +import org.etsi.osl.tmf.sim638.model.ServiceUpdate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +@Component +public class ServiceInventoryValidator implements Validator { + @Autowired + private ServiceSpecificationRepoService serviceSpecificationRepoService; + + @Override + public boolean supports(Class clazz) { + return ServiceUpdate.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + ServiceUpdate update = (ServiceUpdate) target; + if (update.getServiceCharacteristic() == null || update.getServiceSpecificationRef() == null) { + return; + } + String serviceSpecificationId = update.getServiceSpecificationRef().getId(); + ServiceSpecification serviceSpecification = serviceSpecificationRepoService.findByUuid(serviceSpecificationId); + CharacteristicParser characteristicParser = new CharacteristicParser(); + for (Characteristic characteristic: update.getServiceCharacteristic()) { + ServiceSpecCharacteristic serviceSpecCharacteristic = serviceSpecification.findSpecCharacteristicByName(characteristic.getName()); + serviceSpecCharacteristic.setServiceSpecCharacteristicValue(characteristicParser.toSetOfCharacteristicValues(characteristic)); + if (serviceSpecCharacteristic.getServiceSpecCharacteristicValue().stream() + .anyMatch(value -> { + ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = + new ServiceSpecCharacteristicValueValidator(value); + return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); + })) { + errors.reject("invalid.request"); + return; + } + } + } +} diff --git a/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java index c5befc9a..47b7902c 100644 --- a/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java @@ -246,7 +246,7 @@ public class ServiceApiControllerTest extends BaseIT { private ServiceOrderCreate createServiceOrderWithCharacteristicValue(String characteristicValue) throws Exception { - File sspec = new File("src/test/resources/testServiceSpecValidRangeInterval.json"); + File sspec = new File("src/test/resources/reposervices/scm633/testServiceSpecValidRangeInterval.json"); InputStream in = new FileInputStream(sspec); String sspectext = IOUtils.toString(in, "UTF-8"); -- GitLab From 16152cc52b59a0dbae0a13a2ece3ff21e81165bf Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Nov 2025 17:07:43 +0000 Subject: [PATCH 05/11] ServiceOrder Validator and fixed resource path in tests --- .../so641/api/ServiceOrderApiController.java | 16 +++-- .../osl/tmf/util/ServiceOrderValidator.java | 58 +++++++++++++++++++ .../so641/ServiceOrderApiControllerTest.java | 3 +- 3 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java diff --git a/src/main/java/org/etsi/osl/tmf/so641/api/ServiceOrderApiController.java b/src/main/java/org/etsi/osl/tmf/so641/api/ServiceOrderApiController.java index bd0a98d1..77bd39e8 100644 --- a/src/main/java/org/etsi/osl/tmf/so641/api/ServiceOrderApiController.java +++ b/src/main/java/org/etsi/osl/tmf/so641/api/ServiceOrderApiController.java @@ -32,6 +32,7 @@ import org.etsi.osl.tmf.so641.model.ServiceOrderCreate; import org.etsi.osl.tmf.so641.model.ServiceOrderUpdate; import org.etsi.osl.tmf.so641.reposervices.ServiceOrderRepoService; import org.etsi.osl.tmf.util.AddUserAsOwnerToRelatedParties; +import org.etsi.osl.tmf.util.ServiceOrderValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -46,10 +47,8 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.*; import org.etsi.osl.model.nfv.UserRoleType; import io.swagger.v3.oas.annotations.Parameter; @@ -79,6 +78,15 @@ public class ServiceOrderApiController implements ServiceOrderApi { @Autowired ServiceOrderApiRouteBuilder serviceOrderApiRouteBuilder; + @Autowired + private ServiceOrderValidator serviceOrderValidator; + + // Custom validation resulting from ServiceSpecCharacteristicValue range interval and type validation (https://labs.etsi.org/rep/groups/osl/code/-/epics/30) + @InitBinder + protected void initBinder(WebDataBinder binder) { + binder.addValidators(serviceOrderValidator); + } + @org.springframework.beans.factory.annotation.Autowired public ServiceOrderApiController(ObjectMapper objectMapper, HttpServletRequest request) { this.objectMapper = objectMapper; diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java new file mode 100644 index 00000000..90568670 --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java @@ -0,0 +1,58 @@ +package org.etsi.osl.tmf.util; + +import org.etsi.osl.tmf.common.model.service.Characteristic; +import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristic; +import org.etsi.osl.tmf.scm633.model.ServiceSpecification; +import org.etsi.osl.tmf.scm633.reposervices.ServiceSpecificationRepoService; +import org.etsi.osl.tmf.so641.model.ServiceOrderCreate; +import org.etsi.osl.tmf.so641.model.ServiceOrderItem; +import org.etsi.osl.tmf.so641.model.ServiceOrderUpdate; +import org.etsi.osl.tmf.so641.model.ServiceRestriction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +@Component +public class ServiceOrderValidator implements Validator { + @Autowired + private ServiceSpecificationRepoService serviceSpecificationRepoService; + + @Override + public boolean supports(Class clazz) { + return ServiceOrderCreate.class.isAssignableFrom(clazz) || ServiceOrderUpdate.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + if (!(target instanceof ServiceOrderCreate)) { + return; + } + ServiceOrderCreate create = (ServiceOrderCreate) target; + for (ServiceOrderItem orderItem: create.getOrderItem()) { + if (orderItem.getService() == null) { + return; + } + ServiceRestriction service = orderItem.getService(); + if (service.getServiceCharacteristic() == null || service.getServiceSpecification() == null) { + return; + } + String serviceSpecificationId = service.getServiceSpecification().getId(); + ServiceSpecification serviceSpecification = serviceSpecificationRepoService.findByUuid(serviceSpecificationId); + CharacteristicParser characteristicParser = new CharacteristicParser(); + for (Characteristic characteristic: service.getServiceCharacteristic()) { + ServiceSpecCharacteristic serviceSpecCharacteristic = serviceSpecification.findSpecCharacteristicByName(characteristic.getName()); + serviceSpecCharacteristic.setServiceSpecCharacteristicValue(characteristicParser.toSetOfCharacteristicValues(characteristic)); + if (serviceSpecCharacteristic.getServiceSpecCharacteristicValue().stream() + .anyMatch(value -> { + ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = + new ServiceSpecCharacteristicValueValidator(value); + return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); + })) { + errors.reject("invalid.request"); + return; + } + } + } + } +} diff --git a/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java index c94290e5..8c39beb1 100644 --- a/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java @@ -430,12 +430,11 @@ public class ServiceOrderApiControllerTest extends BaseIT { private ServiceOrderCreate createServiceOrderWithCharacteristicValue(String characteristicValue) throws Exception { - File sspec = new File("src/test/resources/testServiceSpecValidRangeInterval.json"); + File sspec = new File("src/test/resources/reposervices/scm633/testServiceSpecValidRangeInterval.json"); InputStream in = new FileInputStream(sspec); String sspectext = IOUtils.toString(in, "UTF-8"); ServiceSpecificationCreate sspeccr = JsonUtils.toJsonObj(sspectext, ServiceSpecificationCreate.class); - sspeccr.setName("Spec1"); ServiceSpecification responsesSpec = createServiceSpec(sspeccr); ServiceOrderCreate serviceOrder = new ServiceOrderCreate(); -- GitLab From 881d15f14f78ce7af26e8451cdb75640ccebc9d1 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Tue, 18 Nov 2025 15:42:03 +0000 Subject: [PATCH 06/11] ServiceOrderApi tests passing, ServiceInventoryApi tests still not passing --- .../osl/tmf/util/CharacteristicParser.java | 40 +++++++++---------- .../tmf/util/ServiceInventoryValidator.java | 2 +- .../osl/tmf/util/ServiceOrderValidator.java | 2 +- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java b/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java index 270b529c..80fe5edc 100644 --- a/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java +++ b/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java @@ -7,42 +7,38 @@ import org.etsi.osl.tmf.common.model.Any; import org.etsi.osl.tmf.common.model.service.Characteristic; import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristicValue; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.*; public class CharacteristicParser { private static final ObjectMapper mapper = new ObjectMapper(); - public Set toSetOfCharacteristicValues(Characteristic characteristic) { - Set result = new HashSet<>(); + public void updateServiceSpecCharacteristicValues( + Set serviceSpecCharacteristicValues, + Characteristic characteristic + ) { Any input = characteristic.getValue(); if (input == null) { - return result; + return; } + List values = new ArrayList<>(); try { JsonNode node = mapper.readTree(input.getValue()); if (node.isArray()) { - Set values = mapper.readValue(input.getValue(), new TypeReference>() {}); - result = values.stream().map(value -> { - ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = new ServiceSpecCharacteristicValue(); - serviceSpecCharacteristicValue.setValue(value); - return serviceSpecCharacteristicValue; - }).collect(Collectors.toSet()); + values = mapper.readValue(input.getValue(), new TypeReference<>() {}); } else if (node.isObject()) { - ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = new ServiceSpecCharacteristicValue(); - serviceSpecCharacteristicValue.setValue(mapper.treeToValue(node, Any.class)); - result.add(serviceSpecCharacteristicValue); + values.add(mapper.treeToValue(node, Any.class)); } else { - ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = new ServiceSpecCharacteristicValue(); - serviceSpecCharacteristicValue.setValue(input); - result.add(serviceSpecCharacteristicValue); + values.add(input); } } catch (Exception e) { - ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = new ServiceSpecCharacteristicValue(); - serviceSpecCharacteristicValue.setValue(input); - result.add(serviceSpecCharacteristicValue); + values.add(input); + } + Iterator serviceSpecCharacteristicValueIterator = serviceSpecCharacteristicValues.iterator(); + Iterator valueIterator = values.iterator(); + while (serviceSpecCharacteristicValueIterator.hasNext() && valueIterator.hasNext()) { + ServiceSpecCharacteristicValue serviceSpecCharacteristicValue = serviceSpecCharacteristicValueIterator.next(); + Any value = valueIterator.next(); + serviceSpecCharacteristicValue.setValue(value); } - return result; } } diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java index b42401e8..7dcfeb71 100644 --- a/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java @@ -31,7 +31,7 @@ public class ServiceInventoryValidator implements Validator { CharacteristicParser characteristicParser = new CharacteristicParser(); for (Characteristic characteristic: update.getServiceCharacteristic()) { ServiceSpecCharacteristic serviceSpecCharacteristic = serviceSpecification.findSpecCharacteristicByName(characteristic.getName()); - serviceSpecCharacteristic.setServiceSpecCharacteristicValue(characteristicParser.toSetOfCharacteristicValues(characteristic)); + characteristicParser.updateServiceSpecCharacteristicValues(serviceSpecCharacteristic.getServiceSpecCharacteristicValue(), characteristic); if (serviceSpecCharacteristic.getServiceSpecCharacteristicValue().stream() .anyMatch(value -> { ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java index 90568670..cb88d1bc 100644 --- a/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java @@ -42,7 +42,7 @@ public class ServiceOrderValidator implements Validator { CharacteristicParser characteristicParser = new CharacteristicParser(); for (Characteristic characteristic: service.getServiceCharacteristic()) { ServiceSpecCharacteristic serviceSpecCharacteristic = serviceSpecification.findSpecCharacteristicByName(characteristic.getName()); - serviceSpecCharacteristic.setServiceSpecCharacteristicValue(characteristicParser.toSetOfCharacteristicValues(characteristic)); + characteristicParser.updateServiceSpecCharacteristicValues(serviceSpecCharacteristic.getServiceSpecCharacteristicValue(), characteristic); if (serviceSpecCharacteristic.getServiceSpecCharacteristicValue().stream() .anyMatch(value -> { ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = -- GitLab From 73e359d24e2bbeac43ef6113516f6fe7383f11ab Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Tue, 18 Nov 2025 16:06:11 +0000 Subject: [PATCH 07/11] ServiceOrderApi tests now passing --- .../api/sim638/ServiceApiControllerTest.java | 33 +++++++------------ .../so641/ServiceOrderApiControllerTest.java | 1 + 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java index 47b7902c..6f3f1761 100644 --- a/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java @@ -180,20 +180,21 @@ public class ServiceApiControllerTest extends BaseIT { @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) @Test public void testServiceInvalidRangeIntervalIsBadRequest() throws Exception { - ServiceOrderCreate serviceOrder = createServiceOrderWithCharacteristicValue("9000"); + ServiceCreate service = createServiceWithCharacteristicValue("9000"); mvc.perform(MockMvcRequestBuilders.post("/serviceInventory/v4/service") .with(SecurityMockMvcRequestPostProcessors.csrf()) - .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(service))) .andExpect(status().isBadRequest()); } + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) @Test public void testServiceInvalidTypesIsBadRequest() throws Exception { - ServiceOrderCreate serviceOrder = createServiceOrderWithCharacteristicValue("not an integer"); + ServiceCreate service = createServiceWithCharacteristicValue("not an integer"); mvc.perform(MockMvcRequestBuilders.post("/serviceInventory/v4/service") .with(SecurityMockMvcRequestPostProcessors.csrf()) - .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(service))) .andExpect(status().isBadRequest()); } @@ -245,38 +246,26 @@ public class ServiceApiControllerTest extends BaseIT { } - private ServiceOrderCreate createServiceOrderWithCharacteristicValue(String characteristicValue) throws Exception { + private ServiceCreate createServiceWithCharacteristicValue(String characteristicValue) throws Exception { File sspec = new File("src/test/resources/reposervices/scm633/testServiceSpecValidRangeInterval.json"); InputStream in = new FileInputStream(sspec); String sspectext = IOUtils.toString(in, "UTF-8"); + ServiceCreate service = new ServiceCreate(); ServiceSpecificationCreate sspeccr = JsonUtils.toJsonObj(sspectext, ServiceSpecificationCreate.class); sspeccr.setName("Spec1"); ServiceSpecification responsesSpec = createServiceSpec(sspeccr); - - ServiceOrderCreate serviceOrder = new ServiceOrderCreate(); - serviceOrder.setCategory("Test Category"); - serviceOrder.setDescription("A Test Service"); - serviceOrder.setRequestedStartDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); - serviceOrder.setRequestedCompletionDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); - - ServiceOrderItem soi = new ServiceOrderItem(); - serviceOrder.getOrderItem().add(soi); - soi.setState(ServiceOrderStateType.ACKNOWLEDGED); - - ServiceRestriction serviceRestriction = new ServiceRestriction(); ServiceSpecificationRef aServiceSpecificationRef = new ServiceSpecificationRef(); aServiceSpecificationRef.setId(responsesSpec.getId()); aServiceSpecificationRef.setName(responsesSpec.getName()); + service.setServiceSpecificationRef(aServiceSpecificationRef); - serviceRestriction.setServiceSpecification(aServiceSpecificationRef); - serviceRestriction.setName("aServiceRestriction"); Characteristic characteristic = new Characteristic(); characteristic.setName("Port"); characteristic.setValue(new Any(characteristicValue)); - serviceRestriction.setServiceCharacteristic(Set.of(characteristic)); - soi.setService(serviceRestriction); - return serviceOrder; + service.setServiceCharacteristic(List.of(characteristic)); + + return service; } diff --git a/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java index 8c39beb1..7318123d 100644 --- a/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java @@ -315,6 +315,7 @@ public class ServiceOrderApiControllerTest extends BaseIT { .andExpect(status().isBadRequest()); } + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) @Test public void testCreateServiceOrderInvalidTypesIsBadRequest() throws Exception { -- GitLab From 5e3712d0096f3a9e6e7a8a10587e7890d5a689bf Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Tue, 18 Nov 2025 16:06:11 +0000 Subject: [PATCH 08/11] ServiceInventoryApi tests now passing --- .../api/sim638/ServiceApiControllerTest.java | 33 +++++++------------ .../so641/ServiceOrderApiControllerTest.java | 1 + 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java index 47b7902c..6f3f1761 100644 --- a/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/sim638/ServiceApiControllerTest.java @@ -180,20 +180,21 @@ public class ServiceApiControllerTest extends BaseIT { @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) @Test public void testServiceInvalidRangeIntervalIsBadRequest() throws Exception { - ServiceOrderCreate serviceOrder = createServiceOrderWithCharacteristicValue("9000"); + ServiceCreate service = createServiceWithCharacteristicValue("9000"); mvc.perform(MockMvcRequestBuilders.post("/serviceInventory/v4/service") .with(SecurityMockMvcRequestPostProcessors.csrf()) - .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(service))) .andExpect(status().isBadRequest()); } + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) @Test public void testServiceInvalidTypesIsBadRequest() throws Exception { - ServiceOrderCreate serviceOrder = createServiceOrderWithCharacteristicValue("not an integer"); + ServiceCreate service = createServiceWithCharacteristicValue("not an integer"); mvc.perform(MockMvcRequestBuilders.post("/serviceInventory/v4/service") .with(SecurityMockMvcRequestPostProcessors.csrf()) - .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(service))) .andExpect(status().isBadRequest()); } @@ -245,38 +246,26 @@ public class ServiceApiControllerTest extends BaseIT { } - private ServiceOrderCreate createServiceOrderWithCharacteristicValue(String characteristicValue) throws Exception { + private ServiceCreate createServiceWithCharacteristicValue(String characteristicValue) throws Exception { File sspec = new File("src/test/resources/reposervices/scm633/testServiceSpecValidRangeInterval.json"); InputStream in = new FileInputStream(sspec); String sspectext = IOUtils.toString(in, "UTF-8"); + ServiceCreate service = new ServiceCreate(); ServiceSpecificationCreate sspeccr = JsonUtils.toJsonObj(sspectext, ServiceSpecificationCreate.class); sspeccr.setName("Spec1"); ServiceSpecification responsesSpec = createServiceSpec(sspeccr); - - ServiceOrderCreate serviceOrder = new ServiceOrderCreate(); - serviceOrder.setCategory("Test Category"); - serviceOrder.setDescription("A Test Service"); - serviceOrder.setRequestedStartDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); - serviceOrder.setRequestedCompletionDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); - - ServiceOrderItem soi = new ServiceOrderItem(); - serviceOrder.getOrderItem().add(soi); - soi.setState(ServiceOrderStateType.ACKNOWLEDGED); - - ServiceRestriction serviceRestriction = new ServiceRestriction(); ServiceSpecificationRef aServiceSpecificationRef = new ServiceSpecificationRef(); aServiceSpecificationRef.setId(responsesSpec.getId()); aServiceSpecificationRef.setName(responsesSpec.getName()); + service.setServiceSpecificationRef(aServiceSpecificationRef); - serviceRestriction.setServiceSpecification(aServiceSpecificationRef); - serviceRestriction.setName("aServiceRestriction"); Characteristic characteristic = new Characteristic(); characteristic.setName("Port"); characteristic.setValue(new Any(characteristicValue)); - serviceRestriction.setServiceCharacteristic(Set.of(characteristic)); - soi.setService(serviceRestriction); - return serviceOrder; + service.setServiceCharacteristic(List.of(characteristic)); + + return service; } diff --git a/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java index 8c39beb1..7318123d 100644 --- a/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/so641/ServiceOrderApiControllerTest.java @@ -315,6 +315,7 @@ public class ServiceOrderApiControllerTest extends BaseIT { .andExpect(status().isBadRequest()); } + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) @Test public void testCreateServiceOrderInvalidTypesIsBadRequest() throws Exception { -- GitLab From c50f0611a11c79c40cb1e81f1f763ffddb71b553 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Tue, 18 Nov 2025 16:34:23 +0000 Subject: [PATCH 09/11] Fixed (I think) NullPointerException preventing pipeline completion --- .../osl/tmf/util/CharacteristicParser.java | 5 +++- .../tmf/util/ServiceInventoryValidator.java | 22 ++++++++++-------- .../osl/tmf/util/ServiceOrderValidator.java | 23 +++++++++++-------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java b/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java index 80fe5edc..74a06389 100644 --- a/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java +++ b/src/main/java/org/etsi/osl/tmf/util/CharacteristicParser.java @@ -7,7 +7,10 @@ import org.etsi.osl.tmf.common.model.Any; import org.etsi.osl.tmf.common.model.service.Characteristic; import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristicValue; -import java.util.*; +import java.util.Set; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; public class CharacteristicParser { private static final ObjectMapper mapper = new ObjectMapper(); diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java index 7dcfeb71..a0067b57 100644 --- a/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java @@ -2,6 +2,7 @@ package org.etsi.osl.tmf.util; import org.etsi.osl.tmf.common.model.service.Characteristic; import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristic; +import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristicValue; import org.etsi.osl.tmf.scm633.model.ServiceSpecification; import org.etsi.osl.tmf.scm633.reposervices.ServiceSpecificationRepoService; import org.etsi.osl.tmf.sim638.model.ServiceUpdate; @@ -10,6 +11,8 @@ import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.Validator; +import java.util.Set; + @Component public class ServiceInventoryValidator implements Validator { @Autowired @@ -31,15 +34,16 @@ public class ServiceInventoryValidator implements Validator { CharacteristicParser characteristicParser = new CharacteristicParser(); for (Characteristic characteristic: update.getServiceCharacteristic()) { ServiceSpecCharacteristic serviceSpecCharacteristic = serviceSpecification.findSpecCharacteristicByName(characteristic.getName()); - characteristicParser.updateServiceSpecCharacteristicValues(serviceSpecCharacteristic.getServiceSpecCharacteristicValue(), characteristic); - if (serviceSpecCharacteristic.getServiceSpecCharacteristicValue().stream() - .anyMatch(value -> { - ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = - new ServiceSpecCharacteristicValueValidator(value); - return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); - })) { - errors.reject("invalid.request"); - return; + if (serviceSpecCharacteristic != null) { + Set serviceSpecCharacteristicValues = serviceSpecCharacteristic.getServiceSpecCharacteristicValue(); + characteristicParser.updateServiceSpecCharacteristicValues(serviceSpecCharacteristicValues, characteristic); + if (serviceSpecCharacteristicValues.stream().anyMatch(value -> { + ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = new ServiceSpecCharacteristicValueValidator(value); + return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); + })) { + errors.reject("invalid.request"); + return; + } } } } diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java index cb88d1bc..031ebb7f 100644 --- a/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java @@ -2,6 +2,7 @@ package org.etsi.osl.tmf.util; import org.etsi.osl.tmf.common.model.service.Characteristic; import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristic; +import org.etsi.osl.tmf.scm633.model.ServiceSpecCharacteristicValue; import org.etsi.osl.tmf.scm633.model.ServiceSpecification; import org.etsi.osl.tmf.scm633.reposervices.ServiceSpecificationRepoService; import org.etsi.osl.tmf.so641.model.ServiceOrderCreate; @@ -13,6 +14,8 @@ import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.Validator; +import java.util.Set; + @Component public class ServiceOrderValidator implements Validator { @Autowired @@ -42,15 +45,17 @@ public class ServiceOrderValidator implements Validator { CharacteristicParser characteristicParser = new CharacteristicParser(); for (Characteristic characteristic: service.getServiceCharacteristic()) { ServiceSpecCharacteristic serviceSpecCharacteristic = serviceSpecification.findSpecCharacteristicByName(characteristic.getName()); - characteristicParser.updateServiceSpecCharacteristicValues(serviceSpecCharacteristic.getServiceSpecCharacteristicValue(), characteristic); - if (serviceSpecCharacteristic.getServiceSpecCharacteristicValue().stream() - .anyMatch(value -> { - ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = - new ServiceSpecCharacteristicValueValidator(value); - return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); - })) { - errors.reject("invalid.request"); - return; + if (serviceSpecCharacteristic != null) { + Set serviceSpecCharacteristicValues = serviceSpecCharacteristic.getServiceSpecCharacteristicValue(); + characteristicParser.updateServiceSpecCharacteristicValues(serviceSpecCharacteristicValues, characteristic); + if (serviceSpecCharacteristicValues.stream().anyMatch( + value -> { + ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = new ServiceSpecCharacteristicValueValidator(value); + return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); + })) { + errors.reject("invalid.request"); + return; + } } } } -- GitLab From 8eb0ed80bdbbb693c89196d7cac6d851d09ce297 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Fri, 21 Nov 2025 17:56:10 +0000 Subject: [PATCH 10/11] Fixed datetime formatter not using ISO format --- .../org/etsi/osl/tmf/util/ServiceInventoryValidator.java | 4 ++-- .../java/org/etsi/osl/tmf/util/ServiceOrderValidator.java | 7 +++---- .../tmf/util/ServiceSpecCharacteristicValueValidator.java | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java index a0067b57..8ffb421a 100644 --- a/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceInventoryValidator.java @@ -38,8 +38,8 @@ public class ServiceInventoryValidator implements Validator { Set serviceSpecCharacteristicValues = serviceSpecCharacteristic.getServiceSpecCharacteristicValue(); characteristicParser.updateServiceSpecCharacteristicValues(serviceSpecCharacteristicValues, characteristic); if (serviceSpecCharacteristicValues.stream().anyMatch(value -> { - ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = new ServiceSpecCharacteristicValueValidator(value); - return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); + ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = new ServiceSpecCharacteristicValueValidator(value); + return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); })) { errors.reject("invalid.request"); return; diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java index 031ebb7f..e734699d 100644 --- a/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceOrderValidator.java @@ -48,10 +48,9 @@ public class ServiceOrderValidator implements Validator { if (serviceSpecCharacteristic != null) { Set serviceSpecCharacteristicValues = serviceSpecCharacteristic.getServiceSpecCharacteristicValue(); characteristicParser.updateServiceSpecCharacteristicValues(serviceSpecCharacteristicValues, characteristic); - if (serviceSpecCharacteristicValues.stream().anyMatch( - value -> { - ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = new ServiceSpecCharacteristicValueValidator(value); - return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); + if (serviceSpecCharacteristicValues.stream().anyMatch(value -> { + ServiceSpecCharacteristicValueValidator serviceSpecCharacteristicValueValidator = new ServiceSpecCharacteristicValueValidator(value); + return !serviceSpecCharacteristicValueValidator.validateType() || !serviceSpecCharacteristicValueValidator.isWithinRangeInterval(); })) { errors.reject("invalid.request"); return; diff --git a/src/main/java/org/etsi/osl/tmf/util/ServiceSpecCharacteristicValueValidator.java b/src/main/java/org/etsi/osl/tmf/util/ServiceSpecCharacteristicValueValidator.java index 6df54061..0dc2074e 100644 --- a/src/main/java/org/etsi/osl/tmf/util/ServiceSpecCharacteristicValueValidator.java +++ b/src/main/java/org/etsi/osl/tmf/util/ServiceSpecCharacteristicValueValidator.java @@ -21,7 +21,7 @@ public class ServiceSpecCharacteristicValueValidator { final String INTEGER_REGEX = "[-+]?\\d+"; final String FLOAT_REGEX = "[-+]?\\d*([.,]\\d+)?([eE][-+]?\\d+)?"; final String BOOLEAN_REGEX = "(?i)true|false"; - final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + final DateTimeFormatter ISO_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); if (serviceSpecCharacteristicValue.getValueType() == null) { return true; } @@ -40,7 +40,7 @@ public class ServiceSpecCharacteristicValueValidator { case BOOLEAN -> stringValue.matches(BOOLEAN_REGEX) || stringValue.matches(INTEGER_REGEX); case TIMESTAMP -> { try { - LocalDateTime.parse(stringValue, TIMESTAMP_FORMATTER); + LocalDateTime.parse(stringValue, ISO_DATE_TIME); yield true; } catch (DateTimeParseException e) { yield false; -- GitLab From 46abeb8e370a28471658c9c1f2aa6dc851effde3 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Fri, 21 Nov 2025 18:10:25 +0000 Subject: [PATCH 11/11] Updated test resource to pass ISO_DATE_TIME format --- .../reposervices/scm633/testServiceSpecValidTypes.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/reposervices/scm633/testServiceSpecValidTypes.json b/src/test/resources/reposervices/scm633/testServiceSpecValidTypes.json index b6b49c9a..e2aee03b 100644 --- a/src/test/resources/reposervices/scm633/testServiceSpecValidTypes.json +++ b/src/test/resources/reposervices/scm633/testServiceSpecValidTypes.json @@ -384,7 +384,7 @@ "valueType": "TIMESTAMP", "validFor": null, "value": { - "value": "2025-09-03 13:38:01", + "value": "2045-06-30T14:52:18.783+0100", "alias": "" } } -- GitLab