diff --git a/CHANGELOG.md b/CHANGELOG.md index b654c974c1..fa536ab642 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ _**For better traceability add the corresponding GitHub issue number in each cha ## [Unreleased] + +## Added + +- Cucumber test step definitions for Policy Store API (Happy Path) including some test helper utilities. #518 + ## [5.1.0] - 2024-05-06 ### Changed @@ -24,6 +29,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha - Fixed validation of json-schemas - IRS is creating tombstone instead collecting Submodel payload, when it not passes validation of schema #522 + ## [5.0.0] - 2024-04-16 ### Added diff --git a/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestHelper.java b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestHelper.java new file mode 100644 index 0000000000..0f87690246 --- /dev/null +++ b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestHelper.java @@ -0,0 +1,96 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,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.irs.cucumber; + +import static io.restassured.RestAssured.given; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.function.Predicate; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.restassured.RestAssured; +import io.restassured.filter.log.LogDetail; +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.filter.log.ResponseLoggingFilter; +import io.restassured.specification.RequestSpecification; +import org.apache.commons.lang3.StringUtils; + +/** + * Common helper class for E2E tests. + */ +public class E2ETestHelper { + + public static final ObjectMapper objectMapper; + + static { + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + } + + private static final boolean LOG_EVERY_REQUEST_AND_RESPONSE = true; + + public static File getExpectedFile(final String fileName) { + return getFile("expected-files/" + fileName); + } + + public static String getTemplateFileContent(final String fileName) throws IOException { + final File file = getTemplateFile(fileName); + byte[] bytes = Files.readAllBytes(file.toPath()); + return new String(bytes, StandardCharsets.UTF_8); + } + + public static File getTemplateFile(final String fileName) { + return getFile("templates/" + fileName); + } + + public static File getFile(final String path) { + final ClassLoader classLoader = E2ETestHelper.class.getClassLoader(); + final URL ressource = classLoader.getResource(path); + return new File(ressource.getFile()); + } + + public static RequestSpecification givenAuthentication( + AuthenticationProperties.AuthenticationPropertiesBuilder authBuilder) { + final AuthenticationProperties authProperties = authBuilder.build(); + return given().spec(authProperties.getNewAuthenticationRequestSpecification()); + } + + static Predicate startingWith(final String prefix) { + return s -> StringUtils.startsWith(s, prefix); + } + + public static void configureRequestAndResponseLogging() { + if (LOG_EVERY_REQUEST_AND_RESPONSE) { + final LogDetail logDetail = LogDetail.ALL; + final RequestLoggingFilter requestLoggingFilter = new RequestLoggingFilter(logDetail); + final ResponseLoggingFilter responseLoggingFilter = new ResponseLoggingFilter(logDetail); + RestAssured.filters(requestLoggingFilter, responseLoggingFilter); + } else { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(LogDetail.ALL); + } + } +} diff --git a/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestHelperForPolicyStoreApi.java b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestHelperForPolicyStoreApi.java new file mode 100644 index 0000000000..55dccb54c7 --- /dev/null +++ b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestHelperForPolicyStoreApi.java @@ -0,0 +1,260 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,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.irs.cucumber; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelper.givenAuthentication; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelper.objectMapper; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cucumber.java.DataTableType; +import io.restassured.http.ContentType; +import io.restassured.response.ValidatableResponse; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.tractusx.irs.cucumber.AuthenticationProperties.AuthenticationPropertiesBuilder; +import org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.CreatePolicyRequest.CreatePolicyRequestBuilder; +import org.springframework.http.HttpStatus; + +/** + * Helper class for Policy Store API tests. + */ +public class E2ETestHelperForPolicyStoreApi { + + public static final String URL_IRS_POLICIES = "/irs/policies"; + + public static final String QUERYPARAM_BUSINESS_PARTNER_NUMBERS = "businessPartnerNumbers"; + + public static String getPolicyTemplate() throws IOException { + return E2ETestHelper.getTemplateFileContent("policy-for-e2e-tests.json"); + } + + @SuppressWarnings("unchecked") + public static Map>> fetchPoliciesForBpn( + final AuthenticationPropertiesBuilder authenticationPropertiesBuilder, final String bpn) { + + return givenAuthentication(authenticationPropertiesBuilder).queryParam(QUERYPARAM_BUSINESS_PARTNER_NUMBERS, bpn) + .when() + .get(URL_IRS_POLICIES) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(Map.class); + } + + public static ValidatableResponse fetchAllPolicies( + final AuthenticationPropertiesBuilder authenticationPropertiesBuilder) { + return givenAuthentication(authenticationPropertiesBuilder).when().get(URL_IRS_POLICIES).then(); + } + + @Builder + public record CreatePolicyRequest(OffsetDateTime validUntil, String businessPartnerNumber, JsonNode payload) { + } + + @Builder + public record UpdatePolicyRequest(OffsetDateTime validUntil, List businessPartnerNumbers, + List policyIds) { + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class BpnToPolicyId { + private String bpn; + private String policyId; + } + + @DataTableType + public BpnToPolicyId bpnToPolicyEntryTransformer(final Map row) { + return new BpnToPolicyId(row.get("BPN"), row.get("policyId")); + } + + public static HashMap> getExpectedBpnToPolicyIdsMapping( + final List bpnToPolicyIdTable) { + + final HashMap> expectedBpnToPolicyIdsMapping = new HashMap<>(); + bpnToPolicyIdTable.forEach(entry -> { + + HashSet policyIds = expectedBpnToPolicyIdsMapping.get(entry.getBpn()); + if (policyIds == null) { + policyIds = new HashSet<>(); + } + + policyIds.add(entry.getPolicyId()); + + expectedBpnToPolicyIdsMapping.put(entry.getBpn(), policyIds); + }); + return expectedBpnToPolicyIdsMapping; + } + + public static Stream extractPolicyIdsForBpn( + final Map>> bpnToPoliciesMap, final String bpn) { + return extractPolicyIds(bpnToPoliciesMap.get(bpn).stream()); + } + + @SuppressWarnings("rawtypes") + public static Stream extractPoliciesForBpn( + final Map>> bpnToPoliciesMap, final String bpn) { + return extractPolicy(bpnToPoliciesMap.get(bpn).stream()); + } + + public static Stream extractPolicyIdsStartingWith( + final Map>> bpnToPoliciesMap, final String policyIdPrefix) { + return extractPolicyIds(bpnToPoliciesMap).filter(policyId -> StringUtils.startsWith(policyId, policyIdPrefix)); + } + + public static Stream extractPolicyIds( + final Map>> bpnToPoliciesMap) { + return extractPolicyIds(bpnToPoliciesMap.values().stream().flatMap(Collection::stream)); + } + + private static Stream extractPolicyIds(final Stream> linkedHashMapStream) { + return extractPolicy(linkedHashMapStream).map(v -> (String) v.get("policyId")); + } + + @SuppressWarnings("rawtypes") + private static Stream extractPolicy(final Stream> linkedHashMapStream) { + return extractPolicyPayloads(linkedHashMapStream).map(v -> (LinkedHashMap) v.get("policy")); + } + + @SuppressWarnings("rawtypes") + private static Stream extractPolicyPayloads( + final Stream> linkedHashMapStream) { + return linkedHashMapStream.map(v -> (LinkedHashMap) v.get("payload")); + } + + public static JsonNode jsonFromString(final ObjectMapper objectMapper, final String jsonObjectStr) + throws JsonProcessingException { + return objectMapper.readTree(jsonObjectStr); + } + + public static ValidatableResponse updatePolicies( + final AuthenticationPropertiesBuilder authenticationPropertiesBuilder, final List policyIds, + final List businessPartnerNumbers, final String validUntil) { + + final var updatePolicyRequest = UpdatePolicyRequest.builder() + .policyIds(policyIds) + .businessPartnerNumbers(businessPartnerNumbers) + .validUntil(OffsetDateTime.parse(validUntil)) + .build(); + return givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .body(updatePolicyRequest) + .when() + .put(URL_IRS_POLICIES) + .then(); + } + + public static ValidatableResponse registerPolicyForBpn( + final AuthenticationPropertiesBuilder authenticationPropertiesBuilder, final String policyJson, + final String bpn, final String validUntil) { + + final CreatePolicyRequest createPolicyRequest; + try { + CreatePolicyRequestBuilder builder = CreatePolicyRequest.builder(); + if (validUntil != null) { + builder = builder.validUntil(OffsetDateTime.parse(validUntil)); + } + createPolicyRequest = builder.businessPartnerNumber(bpn) + .payload(E2ETestHelperForPolicyStoreApi.jsonFromString(objectMapper, + policyJson)) + .build(); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .body(createPolicyRequest) + .when() + .post(URL_IRS_POLICIES) + .then(); + + } + + public static ValidatableResponse fetchPoliciesForBusinessPartnerNumbers( + final AuthenticationPropertiesBuilder authenticationPropertiesBuilder, + final List businessPartnerNumbers) { + + return givenAuthentication(authenticationPropertiesBuilder).queryParam(QUERYPARAM_BUSINESS_PARTNER_NUMBERS, + businessPartnerNumbers).when().get(URL_IRS_POLICIES).then(); + } + + public static void cleanupPolicyIdsByPrefix(final AuthenticationPropertiesBuilder authenticationPropertiesBuilder, + final String policyIdPrefix) { + fetchPolicyIdsByPrefixSuccessfully(authenticationPropertiesBuilder, policyIdPrefix).forEach( + policyId -> cleanupPolicy(authenticationPropertiesBuilder, policyId)); + } + + public static List fetchPolicyIdsByPrefixSuccessfully( + final AuthenticationPropertiesBuilder authenticationPropertiesBuilder, final String policyIdPrefix) { + final Map>> bpnToPoliciesMap = E2ETestHelperForPolicyStoreApi.fetchAllPoliciesSuccessfully( + authenticationPropertiesBuilder); + return E2ETestHelperForPolicyStoreApi.extractPolicyIdsStartingWith(bpnToPoliciesMap, policyIdPrefix).toList(); + } + + @SuppressWarnings("unchecked") + public static Map>> fetchAllPoliciesSuccessfully( + final AuthenticationPropertiesBuilder authenticationPropertiesBuilder) { + final ValidatableResponse getAllPoliciesResponse = fetchAllPolicies(authenticationPropertiesBuilder); + return getAllPoliciesResponse.statusCode(HttpStatus.OK.value()).extract().body().as(Map.class); + } + + public static void cleanupPolicy(final AuthenticationPropertiesBuilder authenticationPropertiesBuilder, + final String policyId) { + + final ValidatableResponse deleteResponse = deletePolicy(authenticationPropertiesBuilder, policyId); + final int status = deleteResponse.extract().statusCode(); + + assertThat(List.of(HttpStatus.OK.value(), HttpStatus.NOT_FOUND.value())).describedAs( + "Should either return status 200 OK or 404 NOT_FOUND").contains(status); + } + + public static ValidatableResponse deletePolicy( + final AuthenticationPropertiesBuilder authenticationPropertiesBuilder, final String policyId) { + return givenAuthentication(authenticationPropertiesBuilder).pathParam("policyId", policyId) + .when() + .delete(URL_IRS_POLICIES + "/{policyId}") + .then(); + } + + @Data + @NoArgsConstructor + public static final class PolicyAttributes { + private String policyId; + private List bpnls; + private String validUntil; + } +} diff --git a/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitions.java b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitionsForJobApi.java similarity index 79% rename from irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitions.java rename to irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitionsForJobApi.java index c4c4aa7eed..421238f243 100644 --- a/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitions.java +++ b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitionsForJobApi.java @@ -23,9 +23,11 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.cucumber; -import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelper.getExpectedFile; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelper.givenAuthentication; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelper.objectMapper; import java.io.File; import java.io.IOException; @@ -38,11 +40,8 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import io.cucumber.java.After; -import io.cucumber.java.Before; +import io.cucumber.java.BeforeAll; import io.cucumber.java.DataTableType; import io.cucumber.java.PendingException; import io.cucumber.java.Scenario; @@ -69,29 +68,35 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -public class E2ETestStepDefinitions { - private RegisterJob.RegisterJobBuilder registerJobBuilder; - private RegisterBatchOrder.RegisterBatchOrderBuilder registerBatchOrderBuilder; +/** + * Step definitions for Job API. + */ +public class E2ETestStepDefinitionsForJobApi { + + private final RegisterJob.RegisterJobBuilder registerJobBuilder; + + private final RegisterBatchOrder.RegisterBatchOrderBuilder registerBatchOrderBuilder; + private UUID jobId; private UUID orderId; private UUID batchId; + private Jobs completedJob; + private BatchOrderResponse batchOrderResponse; private BatchResponse batchResponse; - private ObjectMapper objectMapper; - private AuthenticationProperties authProperties; - private AuthenticationProperties.AuthenticationPropertiesBuilder authenticationPropertiesBuilder; + private final AuthenticationProperties.AuthenticationPropertiesBuilder authenticationPropertiesBuilder; - @Before - public void setup() { + public E2ETestStepDefinitionsForJobApi() { registerJobBuilder = RegisterJob.builder(); registerBatchOrderBuilder = RegisterBatchOrder.builder(); authenticationPropertiesBuilder = AuthenticationProperties.builder(); + } - objectMapper = new ObjectMapper(); - objectMapper.registerModule(new JavaTimeModule()); - objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + @BeforeAll + public static void configureLogging() { + E2ETestHelper.configureRequestAndResponseLogging(); } @DataTableType @@ -107,7 +112,7 @@ public void theIRSURL(String irsUrl) { @And("the regular user api key") public void theRegularUser() throws PropertyNotFoundException { final String regularUserApiKey = "REGULAR_USER_API_KEY"; - String apiKey = System.getenv(regularUserApiKey); + final String apiKey = System.getenv(regularUserApiKey); if (apiKey != null) { authenticationPropertiesBuilder.apiKey(apiKey); } else { @@ -118,7 +123,7 @@ public void theRegularUser() throws PropertyNotFoundException { @And("the admin user api key") public void theAdminUser() throws PropertyNotFoundException { final String adminUserApiKey = "ADMIN_USER_API_KEY"; - String apiKey = System.getenv(adminUserApiKey); + final String apiKey = System.getenv(adminUserApiKey); if (apiKey != null) { authenticationPropertiesBuilder.apiKey(apiKey); } else { @@ -214,17 +219,16 @@ public void timeout(int timeout) { @When("I get the job-id") public void iGetTheJobId() { final RegisterJob job = registerJobBuilder.build(); - authProperties = authenticationPropertiesBuilder.build(); - - final JobHandle createdJobResponse = given().spec(authProperties.getNewAuthenticationRequestSpecification()) - .contentType(ContentType.JSON) - .body(job) - .when() - .post("/irs/jobs") - .then() - .statusCode(HttpStatus.CREATED.value()) - .extract() - .as(JobHandle.class); + + final JobHandle createdJobResponse = // + givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .body(job) + .when() + .post("/irs/jobs") + .then() + .statusCode(HttpStatus.CREATED.value()) + .extract() + .as(JobHandle.class); assertThat(createdJobResponse.getId()).isNotNull(); jobId = createdJobResponse.getId(); @@ -233,18 +237,16 @@ public void iGetTheJobId() { @When("I get the order-id") public void iGetTheOrderId() { final RegisterBatchOrder order = registerBatchOrderBuilder.build(); - authProperties = authenticationPropertiesBuilder.build(); - - final BatchOrderCreated createdOrderResponse = given().spec( - authProperties.getNewAuthenticationRequestSpecification()) - .contentType(ContentType.JSON) - .body(order) - .when() - .post("/irs/orders") - .then() - .statusCode(HttpStatus.CREATED.value()) - .extract() - .as(BatchOrderCreated.class); + + final BatchOrderCreated createdOrderResponse = // + givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .body(order) + .when() + .post("/irs/orders") + .then() + .statusCode(HttpStatus.CREATED.value()) + .extract() + .as(BatchOrderCreated.class); assertThat(createdOrderResponse.id()).isNotNull(); orderId = createdOrderResponse.id(); @@ -295,49 +297,46 @@ public void iCheckIfTheJobHasStatusWithinMinutes(String status, int maxWaitTime) await().atMost(maxWaitTime, TimeUnit.MINUTES) .with() .pollInterval(Duration.ofSeconds(5L)) - .until(() -> given().spec(authProperties.getNewAuthenticationRequestSpecification()) - .contentType(ContentType.JSON) - .queryParam("returnUncompletedJob", false) - .get("/irs/jobs/" + jobId) - .as(Jobs.class) - .getJob() - .getState() - .equals(JobState.value(status))); - - completedJob = given().spec(authProperties.getNewAuthenticationRequestSpecification()) - .contentType(ContentType.JSON) - .queryParam("returnUncompletedJob", true) - .get("/irs/jobs/" + jobId) - .as(Jobs.class); + .until(() -> givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .queryParam("returnUncompletedJob", + false) + .get("/irs/jobs/" + jobId) + .as(Jobs.class) + .getJob() + .getState() + .equals(JobState.value(status))); + + completedJob = givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .queryParam("returnUncompletedJob", true) + .get("/irs/jobs/" + jobId) + .as(Jobs.class); } @Then("I check, if the order contains {int} batches") public void iCheckIfTheOrderContainsBatches(int batchesSize) { - batchOrderResponse = given().spec(authProperties.getNewAuthenticationRequestSpecification()) - .contentType(ContentType.JSON) - .get("/irs/orders/" + orderId) - .as(BatchOrderResponse.class); + batchOrderResponse = givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .get("/irs/orders/" + orderId) + .as(BatchOrderResponse.class); assertThat(batchOrderResponse.getBatches()).hasSize(batchesSize); } @Then("I check, if the batch contains {int} jobs") public void iCheckIfTheBatchContainsJobs(int jobSize) { - batchResponse = given().spec(authProperties.getNewAuthenticationRequestSpecification()) - .contentType(ContentType.JSON) - .get("/irs/orders/" + orderId + "/batches/" + batchId) - .as(BatchResponse.class); + batchResponse = givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .get("/irs/orders/" + orderId + "/batches/" + + batchId) + .as(BatchResponse.class); assertThat(batchResponse.getJobsInBatchChecksum()).isEqualTo(jobSize); } @Then("I check, if job parameter are set with aspects:") public void iCheckIfJobParameterAreSetWithAspects(List aspects) { - completedJob = given().spec(authProperties.getNewAuthenticationRequestSpecification()) - .contentType(ContentType.JSON) - .queryParam("returnUncompletedJob", true) - .get("/irs/jobs/" + jobId) - .as(Jobs.class); + completedJob = givenAuthentication(authenticationPropertiesBuilder).contentType(ContentType.JSON) + .queryParam("returnUncompletedJob", true) + .get("/irs/jobs/" + jobId) + .as(Jobs.class); assertThat(completedJob.getJob().getParameter().getAspects()).containsAll(aspects); } @@ -385,7 +384,8 @@ public void iCheckIfAreAsExpected(String valueType, String fileName) throws IOEx final List actualSubmodels = completedJob.getSubmodels(); final List expectedSubmodels = getExpectedSubmodels(fileName); assertThat(actualSubmodels).hasSameSizeAs(expectedSubmodels) - .usingRecursiveFieldByFieldElementComparatorIgnoringFields("identification", "contractAgreementId") + .usingRecursiveFieldByFieldElementComparatorIgnoringFields("identification", + "contractAgreementId") .containsAll(expectedSubmodels); } } @@ -454,16 +454,14 @@ public void callbackUrlIs(String callbackUrl) { } private List getExpectedSubmodels(final String fileName) throws IOException { - ClassLoader classLoader = this.getClass().getClassLoader(); - File file = new File(classLoader.getResource("expected-files/" + fileName).getFile()); + final File file = getExpectedFile(fileName); assertThat(file).exists(); final Jobs expectedJob = objectMapper.readValue(file, Jobs.class); return expectedJob.getSubmodels(); } private List getExpectedRelationships(final String fileName) throws IOException { - ClassLoader classLoader = this.getClass().getClassLoader(); - File file = new File(classLoader.getResource("expected-files/" + fileName).getFile()); + final File file = getExpectedFile(fileName); assertThat(file).exists(); final Jobs expectedJob = objectMapper.readValue(file, Jobs.class); return expectedJob.getRelationships(); @@ -499,12 +497,9 @@ public void areEmpty(String valueType) { @After("@INTEGRATION_TEST") public void addJobIdToResult(Scenario scenario) { - scenario.attach(jobId.toString(), MediaType.TEXT_PLAIN_VALUE, "jobId"); - } - - private static class PropertyNotFoundException extends Exception { - public PropertyNotFoundException(final String message) { - super(message); + if (jobId != null) { + scenario.attach(jobId.toString(), MediaType.TEXT_PLAIN_VALUE, "jobId"); } } + } diff --git a/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitionsForPolicyStoreApi.java b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitionsForPolicyStoreApi.java new file mode 100644 index 0000000000..a775f3c0b0 --- /dev/null +++ b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/E2ETestStepDefinitionsForPolicyStoreApi.java @@ -0,0 +1,321 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,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.irs.cucumber; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.cleanupPolicy; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.cleanupPolicyIdsByPrefix; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.extractPoliciesForBpn; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.extractPolicyIdsForBpn; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.fetchAllPolicies; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.fetchAllPoliciesSuccessfully; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.fetchPoliciesForBpn; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.fetchPoliciesForBusinessPartnerNumbers; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.getExpectedBpnToPolicyIdsMapping; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.getPolicyTemplate; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.registerPolicyForBpn; +import static org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.updatePolicies; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.cucumber.java.BeforeAll; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.restassured.response.ValidatableResponse; +import org.eclipse.tractusx.irs.cucumber.AuthenticationProperties.AuthenticationPropertiesBuilder; +import org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.BpnToPolicyId; +import org.eclipse.tractusx.irs.cucumber.E2ETestHelperForPolicyStoreApi.PolicyAttributes; +import org.hamcrest.Matchers; +import org.springframework.http.HttpStatus; + +/** + * Step definitions for Policy Store API. + */ +public class E2ETestStepDefinitionsForPolicyStoreApi { + + // TODO(#573): Cucumber Tests: Allow conflict-free parallel execution + + private final AuthenticationPropertiesBuilder authenticationPropertiesBuilder; + + private PolicyAttributes policyAttributes; + + private Map>> bpnToPoliciesMap; + + private ValidatableResponse getAllPoliciesResponse; + private ValidatableResponse getPolicyForBpnlsResponse; + private ValidatableResponse createPoliciesResponse; + private ValidatableResponse updatePoliciesResponse; + private ValidatableResponse deletePoliciesResponse; + + public E2ETestStepDefinitionsForPolicyStoreApi() { + this.authenticationPropertiesBuilder = AuthenticationProperties.builder(); + } + + // TODO(#571): Find a way to re-use ""the IRS URL {string}" and ""the admin user api key" in all step def classes + + @BeforeAll + public static void configureLogging() { + E2ETestHelper.configureRequestAndResponseLogging(); + } + + @Given("the IRS URL {string} -- policystore") + public void theIRSURL(String irsUrl) { + this.authenticationPropertiesBuilder.uri(irsUrl); + } + + @Given("the admin user api key -- policystore") + public void theAdminUser() throws PropertyNotFoundException { + final String adminUserApiKey = "ADMIN_USER_API_KEY"; + final String apiKey = System.getenv(adminUserApiKey); + if (apiKey != null) { + this.authenticationPropertiesBuilder.apiKey(apiKey); + } else { + throw new PropertyNotFoundException("Environment Variable missing: " + adminUserApiKey); + } + } + + @Given("no policies with prefix {string} exist") + public void cleanupPoliciesWithPrefix(final String policyIdPrefix) { + cleanupPolicyIdsByPrefix(authenticationPropertiesBuilder, policyIdPrefix); + } + + @When("I fetch all policies") + public void iFetchAllPolicies() { + this.getAllPoliciesResponse = fetchAllPolicies(authenticationPropertiesBuilder); + this.bpnToPoliciesMap = getBpnToPoliciesMap(this.getAllPoliciesResponse); + } + + @SuppressWarnings("unchecked") + private Map>> getBpnToPoliciesMap(final ValidatableResponse response) { + if (response.extract().statusCode() == HttpStatus.OK.value()) { + return response.extract().body().as(Map.class); + } else { + return null; + } + } + + @When("I successfully fetch all policies") + public void iFetchAllPoliciesSuccessfully() { + this.bpnToPoliciesMap = fetchAllPoliciesSuccessfully(this.authenticationPropertiesBuilder); + } + + @Then("the fetch all policies response should have HTTP status {int}") + public void theFetchAllPoliciesPoliciesResponseShouldHaveStatus(final int httpStatus) { + this.getAllPoliciesResponse.statusCode(httpStatus); + } + + @When("I fetch policies for BPN {string}") + public void iFetchPoliciesForBpn(final String bpn) { + this.bpnToPoliciesMap = fetchPoliciesForBpn(this.authenticationPropertiesBuilder, bpn); + } + + @When("I fetch policies for BPNs:") + public void iFetchPoliciesForBpn(final List businessPartnerNumbers) { + this.getPolicyForBpnlsResponse = fetchPoliciesForBusinessPartnerNumbers(this.authenticationPropertiesBuilder, + businessPartnerNumbers); + this.bpnToPoliciesMap = getBpnToPoliciesMap(this.getPolicyForBpnlsResponse); + } + + @Then("the fetch policies for BPN response should have HTTP status {int}") + public void theFetchPoliciesForBpnResponseShouldHaveStatus(final int httpStatus) { + this.getPolicyForBpnlsResponse.statusCode(httpStatus); + } + + @Then("the BPN {string} should have the following policies:") + public void theBpnShouldHaveTheFollowingPolicies(final String bpn, final List policyIds) { + final List policyIdsForBpn = extractPolicyIdsForBpn(this.bpnToPoliciesMap, bpn).toList(); + assertThat(policyIdsForBpn).containsAll(policyIds); + } + + @Then("the BPNs should be associated with policies as follows:") + public void theBpnShouldHaveTheFollowingPolicies(final List bpnToPolicyIdTable) { + + final HashMap> expectedBpnToPolicyIdsMapping = getExpectedBpnToPolicyIdsMapping( + bpnToPolicyIdTable); + + expectedBpnToPolicyIdsMapping.forEach((bpn, expectedPolicies) -> { + final List policyIdsForBpn = extractPolicyIdsForBpn(this.bpnToPoliciesMap, bpn).toList(); + assertThat(policyIdsForBpn).as("BPN '%s' should be associated with the expected policies", bpn) + .containsAll(expectedPolicies); + }); + } + + @When("I delete the following policies:") + public void iDeleteTheFollowingPolicies(final List policyIds) { + for (final String policyId : policyIds) { + cleanupPolicy(this.authenticationPropertiesBuilder, policyId); + } + } + + @When("I delete the policy {string}") + public void iDeletePolicyWithPolicyId(final String policyId) { + this.deletePoliciesResponse = E2ETestHelperForPolicyStoreApi.deletePolicy(this.authenticationPropertiesBuilder, + policyId); + } + + @Then("the delete policy response should have HTTP status {int}") + public void theDeletePolicyResponseShouldHaveStatus(final int httpStatus) { + this.deletePoliciesResponse.statusCode(httpStatus); + } + + @Then("the BPN {string} should have {int} policies having policyId starting with {string}") + public void theBpnShouldHavePolicyIdsStartingWith(final String bpn, final int numPolicies, final String prefix) { + final List policyIdsForBpn = extractPolicyIdsForBpn(this.bpnToPoliciesMap, bpn).filter( + E2ETestHelper.startingWith(prefix)).toList(); + assertThat(policyIdsForBpn).hasSize(numPolicies); + } + + @Then("the BPN {string} should have no policies with policyId {string}") + public void theBpnShouldNotHavePolicyId(final String bpn, final String policyId) { + final List policyIdsForBpn = extractPolicyIdsForBpn(this.bpnToPoliciesMap, bpn).filter(policyId::equals) + .toList(); + assertThat(policyIdsForBpn).isEmpty(); + } + + @When("I update policy {string}, BPN {string}, validUntil {string}") + public void iPerformUpdatePolicy(final String policyId, final String bpn, final String validUntil) { + this.updatePoliciesResponse = updatePolicies(this.authenticationPropertiesBuilder, List.of(policyId), + List.of(bpn), validUntil); + } + + @When("I add policyId {string} to given BPNs using validUntil {string}:") + @When("I update policy with policyId {string} and given BPNs using validUntil {string}:") + public void iUpdatePolicyBpns(final String policyId, final String validUntil, List businessPartnerNumbers) { + this.updatePoliciesResponse = updatePolicies(this.authenticationPropertiesBuilder, List.of(policyId), + businessPartnerNumbers, validUntil); + this.updatePoliciesResponse.statusCode(HttpStatus.OK.value()); + } + + @Then("the BPN {string} should have a policy with policyId {string} and validUntil {string}") + @SuppressWarnings({ "rawtypes", + "unchecked" + }) + public void theBpnShouldHaveTheExpectedPolicyWithValidUntil(final String bpn, final String policyId, + final String validUntil) { + + final List policies = extractPoliciesForBpn(this.bpnToPoliciesMap, bpn).toList(); + final List policiesFiltered = policies.stream() + .filter(p -> p.get("policyId").equals(policyId)) + .toList(); + assertThat(policiesFiltered).hasSize(1); + assertThat(policiesFiltered.get(0)).containsEntry("policyId", policyId) // + .containsEntry("validUntil", validUntil); + } + + @When("a policy with policyId {string} is registered for BPN {string} and validUntil {string}") + public void iRegisterAPolicy(final String policyId, final String bpn, final String validUntil) throws IOException { + final String policyJson = getPolicyTemplate().formatted(policyId); + this.createPoliciesResponse = registerPolicyForBpn(this.authenticationPropertiesBuilder, policyJson, bpn, + validUntil); + } + + @Given("I want to register a policy") + @Given("I want to update a policy") + public void iWantToRegisterAPolicy() { + this.policyAttributes = new PolicyAttributes(); + } + + @Given("I want to register a policy with policyId {string}") + @Given("I want to update the policy with policyId {string}") + public void iWantToRegisterAPolicyWithPolicyId(final String policyId) { + iWantToRegisterAPolicy(); + policyShouldHavePolicyId(policyId); + } + + @Given("the policy should have policyId {string}") + public void policyShouldHavePolicyId(final String policyId) { + this.policyAttributes.setPolicyId(policyId); + } + + @Given("the policy should be associated to BPN {string}") + public void policyShouldBeAssociatedToBpn(final String bpn) { + if (this.policyAttributes.getBpnls() == null) { + this.policyAttributes.setBpnls(new ArrayList<>()); + } + this.policyAttributes.getBpnls().add(bpn); + } + + @Given("the policy should be associated to the following BPNs:") + public void policyShouldBeAssociatedToBpn(final List bpnls) { + this.policyAttributes.setBpnls(bpnls); + } + + @Given("the policy should have validUntil {string}") + public void policyShouldHaveValidUntil(final String validUntil) { + this.policyAttributes.setValidUntil(validUntil); + } + + @Given("the policy should have no validUntil") + public void policyShouldHaveNoValidUntil() { + this.policyAttributes.setValidUntil(null); + } + + @When("I register the policy") + public void iTryToRegisterThePolicy() throws IOException { + + this.createPoliciesResponse = null; + this.updatePoliciesResponse = null; + + // 'POST policies' only supports one BPN, therefore if we want to associate a policy with multiple BPNs + // we first need to create it via POST for the first BPN ... + final String policyJson = getPolicyTemplate().formatted(this.policyAttributes.getPolicyId()); + this.createPoliciesResponse = registerPolicyForBpn(this.authenticationPropertiesBuilder, policyJson, + this.policyAttributes.getBpnls().get(0), this.policyAttributes.getValidUntil()); + + if (this.policyAttributes.getBpnls().size() > 1) { + // ... and then add it via 'UPDATE policies' to all BPNs to which it should be associated + // (note that this also update the validUntil). + this.updatePoliciesResponse = updatePolicies(this.authenticationPropertiesBuilder, + List.of(this.policyAttributes.getPolicyId()), this.policyAttributes.getBpnls(), + this.policyAttributes.getValidUntil()); + } + + } + + @When("I update the policy") + public void iUpdateThePolicy() { + this.updatePoliciesResponse = updatePolicies(this.authenticationPropertiesBuilder, + List.of(this.policyAttributes.getPolicyId()), this.policyAttributes.getBpnls(), + this.policyAttributes.getValidUntil()); + } + + @Then("the create policy response should have HTTP status {int} and policyId {string}") + public void theCreatePolicyResponseShouldHaveStatus(final int httpStatus, final String policyId) { + this.createPoliciesResponse.statusCode(httpStatus); + this.createPoliciesResponse.body("policyId", Matchers.equalTo(policyId)); + } + + @Then("the create policy response should have HTTP status {int}") + public void theCreatePolicyResponseShouldHaveStatus(final int httpStatus) { + this.createPoliciesResponse.statusCode(httpStatus); + } + + @Then("the update policy response should have HTTP status {int}") + public void theUpdatePolicyResponseShouldHaveStatus(final int httpStatus) { + this.updatePoliciesResponse.statusCode(httpStatus); + } +} diff --git a/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/PropertyNotFoundException.java b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/PropertyNotFoundException.java new file mode 100644 index 0000000000..72f3435d12 --- /dev/null +++ b/irs-cucumber-tests/src/test/java/org/eclipse/tractusx/irs/cucumber/PropertyNotFoundException.java @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,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.irs.cucumber; + +public class PropertyNotFoundException extends Exception { + public PropertyNotFoundException(final String message) { + super(message); + } +} \ No newline at end of file diff --git a/irs-cucumber-tests/src/test/resources/templates/policy-for-e2e-tests.json b/irs-cucumber-tests/src/test/resources/templates/policy-for-e2e-tests.json new file mode 100644 index 0000000000..bcfccffc15 --- /dev/null +++ b/irs-cucumber-tests/src/test/resources/templates/policy-for-e2e-tests.json @@ -0,0 +1,31 @@ +{ + "@context": { + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "%s", + "policy": { + "odrl:permission": [ + { + "odrl:action": "USE", + "odrl:constraint": { + "odrl:and": [ + { + "odrl:leftOperand": "Membership", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "active" + }, + { + "odrl:leftOperand": "PURPOSE", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "ID 3.1 Trace" + } + ] + } + } + ] + } +} \ No newline at end of file