From ca406f049f7eebbeb009f7e9d058a25cd72a914f Mon Sep 17 00:00:00 2001 From: "Krzysztof Massalski (Extern)" Date: Wed, 1 May 2024 10:25:37 +0200 Subject: [PATCH 1/3] feat(impl):[#567] fix message when wrong direction --- .../eclipse/tractusx/irs/component/enums/Direction.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/enums/Direction.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/enums/Direction.java index f0c5fb55a7..a2b4845a50 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/enums/Direction.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/enums/Direction.java @@ -23,6 +23,8 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.component.enums; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonCreator; @@ -62,7 +64,12 @@ public static Direction fromValue(final String value) { return Stream.of(Direction.values()) .filter(direction -> direction.name.equals(value)) .findFirst() - .orElseThrow(); + .orElseThrow(() -> new NoSuchElementException("Unsupported Direction: " + value + + ". Must be one of: " + supportedDirections())); + } + + private static String supportedDirections() { + return Stream.of(Direction.values()).map(direction -> direction.name).collect(Collectors.joining(", ")); } /** From 6f884c0cecebe9f13e91378c4b4ec570ee81ab6d Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 2 May 2024 10:41:42 +0200 Subject: [PATCH 2/3] fix(documentation): [#567] update documentation --- .../sequence/ess-bottom-up-sequence-customer-highlevel.puml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/uml-diagrams/irs-recursive/ess-bottom-up/sequence/ess-bottom-up-sequence-customer-highlevel.puml b/docs/src/uml-diagrams/irs-recursive/ess-bottom-up/sequence/ess-bottom-up-sequence-customer-highlevel.puml index 756a98fd25..93763345ed 100644 --- a/docs/src/uml-diagrams/irs-recursive/ess-bottom-up/sequence/ess-bottom-up-sequence-customer-highlevel.puml +++ b/docs/src/uml-diagrams/irs-recursive/ess-bottom-up/sequence/ess-bottom-up-sequence-customer-highlevel.puml @@ -43,7 +43,7 @@ end note "bomLifecycle": "asPlannned", "collectAspects": true, "depth": 1, - "direction": "upwards", + "direction": "upward", "keys": [ { "bpn": "BPNL0123456789XX", From d881bcd455a2e28cd1cc92191a62f6465ffbfcf2 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 2 May 2024 12:18:32 +0200 Subject: [PATCH 3/3] fix(documentation): [#567] removed invalid user input from message and added test --- .../irs/controllers/IrsControllerTest.java | 179 +++++++++++++----- .../irs/component/enums/Direction.java | 4 +- 2 files changed, 131 insertions(+), 52 deletions(-) diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsControllerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsControllerTest.java index 7808859fea..4dea897404 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsControllerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsControllerTest.java @@ -31,6 +31,7 @@ import static org.eclipse.tractusx.irs.util.TestMother.registerJobWithoutDepthAndAspect; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -74,6 +75,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.BDDMockito; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -91,9 +93,13 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { "digitalTwinRegistry.type=central" }) -@ActiveProfiles(profiles = { "test", "local" }) +@ActiveProfiles(profiles = { "test", + "local" +}) @Import(TestConfig.class) -@ExtendWith({ MockitoExtension.class, SpringExtension.class }) +@ExtendWith({ MockitoExtension.class, + SpringExtension.class +}) class IrsControllerTest extends ControllerTest { private final UUID jobId = UUID.randomUUID(); @@ -129,25 +135,38 @@ void initiateJobForGlobalAssetId() { final UUID returnedJob = UUID.randomUUID(); Mockito.when(service.registerItemJob(any())).thenReturn(JobHandle.builder().id(returnedJob).build()); - given().port(port).contentType(ContentType.JSON).body(registerJobWithoutDepthAndAspect()).post("/irs/jobs") - .then().statusCode(CREATED.value()).body("id", is(returnedJob.toString())); + given().port(port) + .contentType(ContentType.JSON) + .body(registerJobWithoutDepthAndAspect()) + .post("/irs/jobs") + .then() + .statusCode(CREATED.value()) + .body("id", is(returnedJob.toString())); } @Test void shouldReturnUnauthorizedStatusWhenAuthenticationIsMissing() { Mockito.when(authenticationService.getAuthentication(any(HttpServletRequest.class))) - .thenThrow(new BadCredentialsException("Wrong ApiKey")); - - given().port(port).contentType(ContentType.JSON).body(registerJobWithoutDepthAndAspect()).post("/irs/jobs") - .then().statusCode(UNAUTHORIZED.value()); + .thenThrow(new BadCredentialsException("Wrong ApiKey")); + + given().port(port) + .contentType(ContentType.JSON) + .body(registerJobWithoutDepthAndAspect()) + .post("/irs/jobs") + .then() + .statusCode(UNAUTHORIZED.value()); } @Test void shouldReturnForbiddenStatusWhenRequiredAuthorityIsMissing() { authenticateWith("view_irs_wrong_authority"); - given().port(port).contentType(ContentType.JSON).body(registerJobWithoutDepthAndAspect()).post("/irs/jobs") - .then().statusCode(FORBIDDEN.value()); + given().port(port) + .contentType(ContentType.JSON) + .body(registerJobWithoutDepthAndAspect()) + .post("/irs/jobs") + .then() + .statusCode(FORBIDDEN.value()); } @ParameterizedTest @@ -155,16 +174,52 @@ void shouldReturnForbiddenStatusWhenRequiredAuthorityIsMissing() { void shouldReturnBadRequestWhenRegisterJobBodyNotValid(final RegisterJob registerJob) { authenticateWith(IrsRoles.VIEW_IRS); - given().port(port).contentType(ContentType.JSON).body(registerJob).post("/irs/jobs") - .then().statusCode(BAD_REQUEST.value()); + given().port(port) + .contentType(ContentType.JSON) + .body(registerJob) + .post("/irs/jobs") + .then() + .statusCode(BAD_REQUEST.value()); + } + + @ParameterizedTest + @ValueSource(strings = { "upwards", + "downwards" + }) + void shouldReturnBadRequestWhenRegisterJobWithInvalidDirection(String invalidDirection) { + authenticateWith(IrsRoles.VIEW_IRS); + + given().port(port) + .contentType(ContentType.JSON) + .body(""" + { + "key": { + "globalAssetId": "urn:uuid:c6d2d642-a055-4ddf-87e3-1a3b02c689e3", + "bpn": "BPNL00000000BJTL" + }, + "direction": "", + "lookupBPNs": true + } + """.replace("", invalidDirection)) + .post("/irs/jobs") + .then() + .statusCode(BAD_REQUEST.value()) + .body("error", containsString("Unsupported direction")) + .body("error", containsString("Must be one of: upward, downward")) + // error message should not contain unvalidated user input for security reasons + .body("error", not(containsString(invalidDirection))); } @Test void shouldReturnBadRequestWhenRegisterJobHasWrongCallbackUrl() { authenticateWith(IrsRoles.VIEW_IRS); - given().port(port).contentType(ContentType.JSON).body(registerJobWithUrl("hhh://example.com")).post("/irs/jobs") - .then().statusCode(BAD_REQUEST.value()); + given().port(port) + .contentType(ContentType.JSON) + .body(registerJobWithUrl("hhh://example.com")) + .post("/irs/jobs") + .then() + .statusCode(BAD_REQUEST.value()); } @Test @@ -174,8 +229,12 @@ void shouldAcceptCorrectCallbackUrl() { final UUID returnedJob = UUID.randomUUID(); Mockito.when(service.registerItemJob(any())).thenReturn(JobHandle.builder().id(returnedJob).build()); - given().port(port).contentType(ContentType.JSON).body(registerJobWithUrl("https://example.com")).post("/irs/jobs") - .then().statusCode(CREATED.value()); + given().port(port) + .contentType(ContentType.JSON) + .body(registerJobWithUrl("https://example.com")) + .post("/irs/jobs") + .then() + .statusCode(CREATED.value()); } @Test @@ -191,18 +250,20 @@ void getJobsByState() throws Exception { final String returnJobAsString = objectMapper.writeValueAsString(returnedJob); - Mockito.when(service.getJobsByState(any(), any())).thenReturn( - new PageResult(new PagedListHolder<>(List.of(returnedJob)))); + Mockito.when(service.getJobsByState(any(), any())) + .thenReturn(new PageResult(new PagedListHolder<>(List.of(returnedJob)))); - given().port(port).get("/irs/jobs") - .then().statusCode(OK.value()) + given().port(port) + .get("/irs/jobs") + .then() + .statusCode(OK.value()) .body(containsString(returnJobAsString)) .body(containsString(returnedJob.getId().toString())) .body(containsString(returnedJob.getState().toString())) - .body(containsString(returnedJob.getStartedOn().format(DateTimeFormatter.ofPattern( - "yyyy-MM-dd'T'HH:mm:ss.SSS")))) - .body(containsString(returnedJob.getCompletedOn().format(DateTimeFormatter.ofPattern( - "yyyy-MM-dd'T'HH:mm:ss.SSS")))); + .body(containsString( + returnedJob.getStartedOn().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")))) + .body(containsString( + returnedJob.getCompletedOn().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")))); } @Test @@ -212,19 +273,17 @@ void cancelJobById() { final Job canceledJob = Job.builder().id(jobId).state(JobState.CANCELED).build(); Mockito.when(this.service.cancelJobById(jobId)).thenReturn(canceledJob); - given().port(port).put("/irs/jobs/" + jobId) - .then().statusCode(OK.value()); + given().port(port).put("/irs/jobs/" + jobId).then().statusCode(OK.value()); } @Test void cancelJobById_throwEntityNotFoundException() { authenticateWith(IrsRoles.VIEW_IRS); - BDDMockito.given(this.service.cancelJobById(jobId)).willThrow( - new ResponseStatusException(HttpStatus.NOT_FOUND, "No job exists with id " + jobId)); + BDDMockito.given(this.service.cancelJobById(jobId)) + .willThrow(new ResponseStatusException(HttpStatus.NOT_FOUND, "No job exists with id " + jobId)); - given().port(port).put("/irs/jobs/" + jobId) - .then().statusCode(NOT_FOUND.value()); + given().port(port).put("/irs/jobs/" + jobId).then().statusCode(NOT_FOUND.value()); } @Test @@ -233,8 +292,7 @@ void getJobWithMalformedIdShouldReturnBadRequest() { final String jobIdMalformed = UUID.randomUUID() + "MALFORMED"; - given().port(port).get("/irs/jobs/" + jobIdMalformed) - .then().statusCode(BAD_REQUEST.value()); + given().port(port).get("/irs/jobs/" + jobIdMalformed).then().statusCode(BAD_REQUEST.value()); } @Test @@ -244,19 +302,23 @@ void shouldReturnBadRequestWhenRegisterJobWithMalformedAspectJson() { Mockito.when(service.registerItemJob(any())).thenThrow(IllegalArgumentException.class); final String requestBody = "{ \"aspects\": [ \"MALFORMED\" ], \"globalAssetId\": \"urn:uuid:8a61c8db-561e-4db0-84ec-a693fc5ffdf6\" }"; - given().port(port).contentType(ContentType.JSON).body(requestBody).post("/irs/jobs") - .then().statusCode(BAD_REQUEST.value()); + given().port(port) + .contentType(ContentType.JSON) + .body(requestBody) + .post("/irs/jobs") + .then() + .statusCode(BAD_REQUEST.value()); } @Test void shouldReturnBadRequestWhenCancelingAlreadyCompletedJob() { authenticateWith(IrsRoles.VIEW_IRS); - BDDMockito.given(this.service.cancelJobById(jobId)).willThrow(new IllegalStateException( - format("Cannot transition from state %s to %s", JobState.COMPLETED, JobState.CANCELED))); + BDDMockito.given(this.service.cancelJobById(jobId)) + .willThrow(new IllegalStateException( + format("Cannot transition from state %s to %s", JobState.COMPLETED, JobState.CANCELED))); - given().port(port).put("/irs/jobs/" + jobId) - .then().statusCode(BAD_REQUEST.value()); + given().port(port).put("/irs/jobs/" + jobId).then().statusCode(BAD_REQUEST.value()); } @Test @@ -278,8 +340,14 @@ void shouldReturnAspectModels() throws Exception { BDDMockito.given(this.semanticHubService.getAllAspectModels()).willReturn(aspectModels); - final AspectModels response = given().port(port).get("/irs/aspectmodels") - .then().statusCode(OK.value()).and().extract().response().as(AspectModels.class); + final AspectModels response = given().port(port) + .get("/irs/aspectmodels") + .then() + .statusCode(OK.value()) + .and() + .extract() + .response() + .as(AspectModels.class); assertEquals(aspectModels, response); } @@ -288,8 +356,7 @@ void shouldReturnAspectModels() throws Exception { void shouldReturnForbiddenStatusForAspectModelsWhenRequiredAuthorityIsMissing() { authenticateWith("view_irs_wrong_authority"); - given().port(port).get("/irs/aspectmodels") - .then().statusCode(FORBIDDEN.value()); + given().port(port).get("/irs/aspectmodels").then().statusCode(FORBIDDEN.value()); } @Test @@ -300,10 +367,16 @@ void shouldReturnPartialWhenJobCompleted() { Mockito.when(this.service.getJobForJobId(eq(jobId), anyBoolean())).thenReturn(runningJob); - given().port(port).queryParam("returnUncompletedJob", true).get("/irs/jobs/" + jobId) - .then().statusCode(PARTIAL_CONTENT.value()); - given().port(port).queryParam("returnUncompletedJob", false).get("/irs/jobs/" + jobId) - .then().statusCode(PARTIAL_CONTENT.value()); + given().port(port) + .queryParam("returnUncompletedJob", true) + .get("/irs/jobs/" + jobId) + .then() + .statusCode(PARTIAL_CONTENT.value()); + given().port(port) + .queryParam("returnUncompletedJob", false) + .get("/irs/jobs/" + jobId) + .then() + .statusCode(PARTIAL_CONTENT.value()); } @Test @@ -314,10 +387,16 @@ void shouldReturnOkWhenJobCompleted() { Mockito.when(this.service.getJobForJobId(eq(jobId), anyBoolean())).thenReturn(completedJob); - given().port(port).queryParam("returnUncompletedJob", true).get("/irs/jobs/" + jobId) - .then().statusCode(OK.value()); - given().port(port).queryParam("returnUncompletedJob", false).get("/irs/jobs/" + jobId) - .then().statusCode(OK.value()); + given().port(port) + .queryParam("returnUncompletedJob", true) + .get("/irs/jobs/" + jobId) + .then() + .statusCode(OK.value()); + given().port(port) + .queryParam("returnUncompletedJob", false) + .get("/irs/jobs/" + jobId) + .then() + .statusCode(OK.value()); } } \ No newline at end of file diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/enums/Direction.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/enums/Direction.java index a2b4845a50..dd69c51cb2 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/enums/Direction.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/enums/Direction.java @@ -64,8 +64,8 @@ public static Direction fromValue(final String value) { return Stream.of(Direction.values()) .filter(direction -> direction.name.equals(value)) .findFirst() - .orElseThrow(() -> new NoSuchElementException("Unsupported Direction: " + value - + ". Must be one of: " + supportedDirections())); + .orElseThrow(() -> new NoSuchElementException( + "Unsupported direction. Must be one of: " + supportedDirections())); } private static String supportedDirections() {