diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/ErpAdapterConfiguration.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/ErpAdapterConfiguration.java index 88981664..b3a98e7b 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/ErpAdapterConfiguration.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/ErpAdapterConfiguration.java @@ -40,12 +40,14 @@ public class ErpAdapterConfiguration { @Value("${puris.erpadapter.url}") private String erpAdapterUrl; - /** - * The URL under which we expect responses from - * the ERP adapter - */ - @Value("${puris.baseurl}" + "${server.servlet.context-path}" + "/erp-adapter") - private String erpResponseUrl; + + @Value("${puris.baseurl}") + @Getter(AccessLevel.NONE) + private String purisBaseUrl; + + @Value("${server.servlet.context-path}") + @Getter(AccessLevel.NONE) + private String contextPath; /** * The auth-key used when accessing the ERP adapter's @@ -114,4 +116,16 @@ public long getRefreshInterval() { // translate minutes to milliseconds return refreshInterval * 60 * 1000; } + + /** + * Provides the URL, under which we expect to receive a response from the ERP Adapter + * + * @return the response URL + */ + public String getErpResponseUrl() { + String url = purisBaseUrl.endsWith("/") ? purisBaseUrl : purisBaseUrl + "/"; + String context = contextPath.startsWith("/") ? contextPath.substring(1) : contextPath; + context = context.endsWith("/") ? context : context + "/"; + return url + context + "erp-adapter"; + } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/controller/ErpAdapterController.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/controller/ErpAdapterController.java index d0ae9159..10c8b649 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/controller/ErpAdapterController.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/controller/ErpAdapterController.java @@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; +import org.eclipse.tractusx.puris.backend.erpadapter.domain.model.ErpAdapterRequest; import org.eclipse.tractusx.puris.backend.erpadapter.logic.service.ErpAdapterTriggerService; import org.eclipse.tractusx.puris.backend.erpadapter.logic.service.ItemStockErpAdapterService; import org.eclipse.tractusx.puris.backend.masterdata.logic.service.MaterialPartnerRelationService; @@ -79,7 +80,8 @@ public ResponseEntity scheduleErpUpdate( ) { materialNumber = new String(Base64.getDecoder().decode(materialNumber)); boolean valid = BPNL_PATTERN.matcher(bpnl).matches() - && NON_EMPTY_NON_VERTICAL_WHITESPACE_PATTERN.matcher(materialNumber).matches(); + && NON_EMPTY_NON_VERTICAL_WHITESPACE_PATTERN.matcher(materialNumber).matches() + && ErpAdapterRequest.SUPPORTED_TYPES.contains(assetType); if (valid && mprService.find(bpnl, materialNumber) != null) { erpAdapterTriggerService.notifyPartnerRequest(bpnl, materialNumber, assetType, directionCharacteristic); return ResponseEntity.status(201).build(); @@ -127,6 +129,9 @@ public ResponseEntity putMethod( } Dto dto = new Dto(requestId, partnerBpnl, responseType, sammVersion, new Date(responseTimestamp), requestBody); AssetType assetType = Arrays.stream(AssetType.values()).filter(type -> type.ERP_KEYWORD.equals(responseType)).findFirst().orElse(null); + if (!ErpAdapterRequest.SUPPORTED_TYPES.contains(assetType)) { + return ResponseEntity.badRequest().body("Unsupported Type"); + } int responseCode = 501; switch (assetType) { case ITEM_STOCK_SUBMODEL -> responseCode = itemStockErpAdapterService.receiveItemStockUpdate(dto); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/domain/model/ErpAdapterRequest.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/domain/model/ErpAdapterRequest.java index 4bc6b705..a53b0963 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/domain/model/ErpAdapterRequest.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/domain/model/ErpAdapterRequest.java @@ -23,13 +23,23 @@ import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import jakarta.validation.Payload; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import lombok.*; +import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; import org.eclipse.tractusx.puris.backend.common.util.PatternStore; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.Date; +import java.util.List; import java.util.UUID; @Entity @@ -40,6 +50,9 @@ @NoArgsConstructor @ToString public class ErpAdapterRequest { + + public final static List SUPPORTED_TYPES = List.of(AssetType.ITEM_STOCK_SUBMODEL); + @GeneratedValue @Id private UUID id; @@ -48,9 +61,8 @@ public class ErpAdapterRequest { @NotNull private String partnerBpnl; - @Pattern(regexp = PatternStore.NON_EMPTY_NON_VERTICAL_WHITESPACE_STRING) - @NotNull - private String requestType; + @ValidRequestType + private AssetType requestType; @Pattern(regexp = PatternStore.NON_EMPTY_NON_VERTICAL_WHITESPACE_STRING) @NotNull @@ -68,4 +80,24 @@ public class ErpAdapterRequest { private String ownMaterialNumber; private DirectionCharacteristic directionCharacteristic; + + // AssetType validation helpers: + @Constraint(validatedBy = RequestTypeValidator.class) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RetentionPolicy.RUNTIME) + private @interface ValidRequestType { + String message() default + "Request Type must be listed in SUPPORTED_TYPES"; + + Class[] groups() default {}; + + Class[] payload() default {}; + } + + public static class RequestTypeValidator implements ConstraintValidator { + @Override + public boolean isValid(AssetType value, ConstraintValidatorContext context) { + return value != null && SUPPORTED_TYPES.contains(value); + } + } } diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestClient.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestClient.java index 27401ea2..68d9061d 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestClient.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestClient.java @@ -45,7 +45,7 @@ public class ErpAdapterRequestClient { public Integer sendRequest(ErpAdapterRequest erpAdapterRequest){ HttpUrl.Builder urlBuilder = HttpUrl.parse(erpAdapterConfiguration.getErpAdapterUrl()).newBuilder(); urlBuilder.addQueryParameter("bpnl", erpAdapterRequest.getPartnerBpnl()); - urlBuilder.addQueryParameter("request-type", erpAdapterRequest.getRequestType()); + urlBuilder.addQueryParameter("request-type", erpAdapterRequest.getRequestType().ERP_KEYWORD); urlBuilder.addQueryParameter("request-id", erpAdapterRequest.getId().toString()); urlBuilder.addQueryParameter("samm-version", erpAdapterRequest.getSammVersion()); urlBuilder.addQueryParameter("request-timestamp", String.valueOf(erpAdapterRequest.getRequestDate().getTime())); diff --git a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterTriggerService.java b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterTriggerService.java index de80eb3b..e9b87223 100644 --- a/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterTriggerService.java +++ b/backend/src/main/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterTriggerService.java @@ -109,7 +109,7 @@ public class ErpAdapterTriggerService { DirectionCharacteristic directionCharacteristic = dataset.getDirectionCharacteristic().isEmpty() ? null : DirectionCharacteristic.valueOf(dataset.getDirectionCharacteristic()); request.setDirectionCharacteristic(directionCharacteristic); - request.setRequestType(dataset.getAssetType().ERP_KEYWORD); + request.setRequestType(dataset.getAssetType()); request.setSammVersion(dataset.getAssetType().ERP_SAMMVERSION); executorService.submit(() -> erpAdapterRequestService.createAndSend(request)); @@ -156,7 +156,7 @@ public void notifyPartnerRequest(String partnerBpnl, String ownMaterialNumber, A erpAdapterRequest.setPartnerBpnl(partnerBpnl); erpAdapterRequest.setOwnMaterialNumber(ownMaterialNumber); erpAdapterRequest.setDirectionCharacteristic(direction); - erpAdapterRequest.setRequestType(type.ERP_KEYWORD); + erpAdapterRequest.setRequestType(type); erpAdapterRequest.setSammVersion(type.ERP_SAMMVERSION); executorService.submit(() -> erpAdapterRequestService.createAndSend(erpAdapterRequest)); diff --git a/backend/src/test/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestClientTest.java b/backend/src/test/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestClientTest.java index 6b018e5e..adb0af4b 100644 --- a/backend/src/test/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestClientTest.java +++ b/backend/src/test/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestClientTest.java @@ -25,6 +25,7 @@ import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.assertj.core.api.Assertions; +import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; import org.eclipse.tractusx.puris.backend.erpadapter.ErpAdapterConfiguration; import org.eclipse.tractusx.puris.backend.erpadapter.domain.model.ErpAdapterRequest; import org.eclipse.tractusx.puris.backend.stock.logic.dto.itemstocksamm.DirectionCharacteristic; @@ -36,6 +37,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; + import java.io.InputStream; import java.util.Date; import java.util.Map; @@ -67,7 +69,7 @@ public class ErpAdapterRequestClientTest { private static final String apiSecret = "my-secret"; - private static final String requestType = "itemstock"; + private static final AssetType requestType = AssetType.ITEM_STOCK_SUBMODEL; private static final String sammVersion = "2.0"; @@ -118,7 +120,7 @@ public void test_should_success() throws Exception { Assertions.assertThat(parameters.size()).isEqualTo(5); Assertions.assertThat(parameters.get("bpnl")).isEqualTo(supplierPartnerBpnl); - Assertions.assertThat(parameters.get("request-type")).isEqualTo(requestType); + Assertions.assertThat(parameters.get("request-type")).isEqualTo(requestType.ERP_KEYWORD); Assertions.assertThat(parameters.get("samm-version")).isEqualTo(sammVersion); Assertions.assertThat(parameters.get("request-timestamp")).isEqualTo(String.valueOf(erpAdapterRequest.getRequestDate().getTime())); Assertions.assertThat(parameters.get("request-id")).isEqualTo(uuid.toString()); diff --git a/backend/src/test/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestValidationTest.java b/backend/src/test/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestValidationTest.java new file mode 100644 index 00000000..a801ecfe --- /dev/null +++ b/backend/src/test/java/org/eclipse/tractusx/puris/backend/erpadapter/logic/service/ErpAdapterRequestValidationTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. + * (represented by Fraunhofer ISST) + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.puris.backend.erpadapter.logic.service; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.eclipse.tractusx.puris.backend.common.edc.domain.model.AssetType; +import org.eclipse.tractusx.puris.backend.erpadapter.domain.model.ErpAdapterRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Date; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +public class ErpAdapterRequestValidationTest { + + private static final String matNbrCustomer = "MNR-7307-AU340474.002"; + + private static final String supplierPartnerBpnl = "BPNL1234567890ZZ"; + + private static Validator validator; + + @BeforeEach + void setUp() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + @ParameterizedTest + @EnumSource(AssetType.class) + public void testRequestTypeValidation(AssetType type) { + // given + ErpAdapterRequest erpAdapterRequest = ErpAdapterRequest.builder() + .id(UUID.randomUUID()) + .requestDate(new Date()) + .partnerBpnl(supplierPartnerBpnl) + .ownMaterialNumber(matNbrCustomer) + .requestType(type) + .sammVersion(type.ERP_SAMMVERSION) + .build(); + + // when + var violations = validator.validate(erpAdapterRequest); + + // then + if (!ErpAdapterRequest.SUPPORTED_TYPES.contains(type)) { + assertEquals(1, violations.size(), "Expected validation errors for unsupported type: " + type); + } else { + assertTrue(violations.isEmpty(), "No validation errors expected for supported type: " + type); + } + } + + @Test + public void testRequestTypeValidation() { + // given + ErpAdapterRequest erpAdapterRequest = ErpAdapterRequest.builder() + .id(UUID.randomUUID()) + .requestDate(null) // must not be null + .partnerBpnl("wrong-bpnl") // should fail regex check + .ownMaterialNumber("illegal-material-number\n") // should fail regex check + .requestType(AssetType.ITEM_STOCK_SUBMODEL) + .sammVersion(AssetType.ITEM_STOCK_SUBMODEL.ERP_SAMMVERSION) + .build(); + + // when + var violations = validator.validate(erpAdapterRequest); + + // then + assertEquals(3, violations.size()); + } + + +} diff --git a/backend/src/test/java/org/eclipse/tractusx/puris/backend/masterdata/controller/PartnerControllerTest.java b/backend/src/test/java/org/eclipse/tractusx/puris/backend/masterdata/controller/PartnerControllerTest.java index e4295c26..a535fc02 100644 --- a/backend/src/test/java/org/eclipse/tractusx/puris/backend/masterdata/controller/PartnerControllerTest.java +++ b/backend/src/test/java/org/eclipse/tractusx/puris/backend/masterdata/controller/PartnerControllerTest.java @@ -24,6 +24,7 @@ import org.eclipse.tractusx.puris.backend.common.security.SecurityConfig; import org.eclipse.tractusx.puris.backend.common.security.annotation.WithMockApiKey; import org.eclipse.tractusx.puris.backend.common.security.logic.ApiKeyAuthenticationProvider; +import org.eclipse.tractusx.puris.backend.common.util.VariablesService; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Address; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Partner; import org.eclipse.tractusx.puris.backend.masterdata.domain.model.Site; @@ -63,6 +64,9 @@ public class PartnerControllerTest { @MockBean private MaterialPartnerRelationService mprService; + @MockBean + private VariablesService variablesService; + private final ModelMapper modelMapper = new ModelMapper(); private final String bpnl = "BPNL2222222222RR"; private final String edcUrl = "https://example.com";