diff options
10 files changed, 301 insertions, 32 deletions
diff --git a/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy b/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy index 5c935e9081..250cdda0a1 100644 --- a/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy +++ b/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy @@ -19,29 +19,30 @@ */ package org.onap.so.bpmn.common.scripts -import org.camunda.bpm.engine.delegate.BpmnError + + import org.camunda.bpm.engine.delegate.DelegateExecution -import org.onap.so.bpmn.common.scripts.AbstractServiceTaskProcessor; +import org.onap.logging.ref.slf4j.ONAPLogConstants import org.onap.so.client.HttpClient +import org.onap.so.client.HttpClientFactory import org.onap.so.logger.MsoLogger -import org.apache.commons.lang3.StringEscapeUtils -import java.util.regex.Matcher -import java.util.regex.Pattern +import org.onap.so.utils.TargetEntity import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response -import org.onap.so.utils.TargetEntity +import java.util.regex.Matcher +import java.util.regex.Pattern class ExternalAPIUtil { String Prefix="EXTAPI_" - public MsoUtils utils = new MsoUtils() - - ExceptionUtil exceptionUtil = new ExceptionUtil() - private static final MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL, ExternalAPIUtil.class) + private final HttpClientFactory httpClientFactory; + private final MsoUtils utils; + private final ExceptionUtil exceptionUtil; + public static final String PostServiceOrderRequestsTemplate = "{\n" + "\t\"externalId\": <externalId>,\n" + @@ -85,7 +86,10 @@ class ExternalAPIUtil { "\t} \n" + "}" - public ExternalAPIUtil() { + ExternalAPIUtil(HttpClientFactory httpClientFactory, MsoUtils utils, ExceptionUtil exceptionUtil) { + this.httpClientFactory = httpClientFactory + this.utils = utils + this.exceptionUtil = exceptionUtil } // public String getUri(DelegateExecution execution, resourceName) { @@ -137,8 +141,7 @@ class ExternalAPIUtil { msoLogger.debug( "Generated uuid is: " + uuid) msoLogger.debug( "URL to be used is: " + url) - URL Url = new URL(url) - HttpClient client = new HttpClient(Url, MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL) + HttpClient client = httpClientFactory.create(new URL(url), MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL) client.addBasicAuthHeader(execution.getVariable("URN_externalapi_auth"), execution.getVariable("URN_mso_msoKey")) client.addAdditionalHeader("X-FromAppId", "MSO") client.addAdditionalHeader(ONAPLogConstants.Headers.REQUEST_ID, uuid) @@ -166,17 +169,15 @@ class ExternalAPIUtil { * @return Response * */ - public Response executeExternalAPIPostCall(DelegateExecution execution, String urlString, String payload){ + public Response executeExternalAPIPostCall(DelegateExecution execution, String url, String payload){ msoLogger.debug( " ======== Started Execute ExternalAPI Post Process ======== ") Response apiResponse = null try{ String uuid = utils.getRequestID() msoLogger.debug( "Generated uuid is: " + uuid) - msoLogger.debug( "URL to be used is: " + urlString) - - URL url = new URL(urlString); + msoLogger.debug( "URL to be used is: " + url) - HttpClient httpClient = new HttpClient(url, "application/json", TargetEntity.AAI) + HttpClient httpClient = httpClientFactory.create(new URL(url), MediaType.APPLICATION_JSON, TargetEntity.AAI) httpClient.addBasicAuthHeader(execution.getVariable("URN_externalapi_auth"), execution.getVariable("URN_mso_msoKey")) httpClient.addAdditionalHeader("X-FromAppId", "MSO") httpClient.addAdditionalHeader("X-TransactionId", uuid) diff --git a/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilFactory.groovy b/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilFactory.groovy new file mode 100644 index 0000000000..e7f46464ee --- /dev/null +++ b/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilFactory.groovy @@ -0,0 +1,29 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - SO + * ================================================================================ + * Copyright (C) 2018 Nokia. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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. + * ============LICENSE_END========================================================= + */ +package org.onap.so.bpmn.common.scripts + +import org.onap.so.client.HttpClientFactory + +class ExternalAPIUtilFactory { + + ExternalAPIUtil create() { + return new ExternalAPIUtil(new HttpClientFactory(), new MsoUtils(), new ExceptionUtil()) + } +} diff --git a/bpmn/MSOCommonBPMN/src/test/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilTest.groovy b/bpmn/MSOCommonBPMN/src/test/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilTest.groovy new file mode 100644 index 0000000000..5f428f1f9f --- /dev/null +++ b/bpmn/MSOCommonBPMN/src/test/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilTest.groovy @@ -0,0 +1,199 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - SO + * ================================================================================ + * Copyright (C) 2018 Nokia. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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. + * ============LICENSE_END========================================================= + */ +package org.onap.so.bpmn.common.scripts + +import org.assertj.core.api.AbstractAssert +import org.camunda.bpm.engine.delegate.BpmnError +import org.camunda.bpm.engine.delegate.DelegateExecution +import org.junit.Test +import org.onap.logging.ref.slf4j.ONAPLogConstants +import org.onap.so.client.HttpClient +import org.onap.so.client.HttpClientFactory +import org.onap.so.utils.TargetEntity +import org.springframework.http.HttpStatus + +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response + +import static org.assertj.core.api.Assertions.assertThat +import static org.assertj.core.api.Assertions.catchThrowableOfType +import static org.mockito.BDDMockito.given +import static org.mockito.BDDMockito.then +import static org.mockito.BDDMockito.willThrow +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.times + +class ExternalAPIUtilTest { + + private static final String URL = "http://someUrl" + private static final String UUID_STR = UUID.nameUUIDFromBytes("deterministic_uuid".getBytes()) + private static final String BODY_PAYLOAD = "payload" + + @Test + void executeExternalAPIGetCall_shouldPerformRestGetCall_withAuthorizationHeaderSet() { + // GIVEN + Response expectedResponse = createExpectedResponse(HttpStatus.ACCEPTED, BODY_PAYLOAD) + HttpClient httpClient = mock(HttpClient.class) + given(httpClient.get()).willReturn(expectedResponse) + HttpClientFactory httpClientFactory = mock(HttpClientFactory.class) + given(httpClientFactory.create(new URL(URL), MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL)).willReturn(httpClient) + + // WHEN + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil(httpClientFactory, new DummyMsoUtils(UUID_STR), new ExceptionUtil()) + Response apiResponse = externalAPIUtil.executeExternalAPIGetCall(createDelegateExecution(), URL) + + // THEN + then(httpClient).should(times(1)).addBasicAuthHeader("value_externalapi_auth", "value_mso_msoKey") + then(httpClient).should(times(1)).addAdditionalHeader("X-FromAppId", "MSO") + then(httpClient).should(times(1)).addAdditionalHeader(ONAPLogConstants.Headers.REQUEST_ID, UUID_STR) + then(httpClient).should(times(1)).addAdditionalHeader("Accept", MediaType.APPLICATION_JSON) + ResponseAssert.assertThat(apiResponse) + .hasStatusCode(HttpStatus.ACCEPTED) + .hasBody(BODY_PAYLOAD) + } + + @Test + void executeExternalAPIGetCall_shouldHandleExceptionsThrownByGetCall_andRethrowAsBpmnError() { + // GIVEN + HttpClient httpClient = mock(HttpClient.class) + willThrow(new RuntimeException("error occurred")).given(httpClient).get() + HttpClientFactory httpClientFactory = mock(HttpClientFactory.class) + given(httpClientFactory.create(new URL(URL), MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL)).willReturn(httpClient) + DelegateExecution delegateExecution = createDelegateExecution() + DummyExceptionUtil exceptionUtil = new DummyExceptionUtil() + + // WHEN + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil(httpClientFactory, new DummyMsoUtils(UUID_STR), exceptionUtil) + BpmnError bpmnError = catchThrowableOfType({ -> externalAPIUtil.executeExternalAPIGetCall(delegateExecution, URL) + }, BpmnError.class) + + // THEN + assertThat(exceptionUtil.getDelegateExecution()).isSameAs(delegateExecution) + assertThat(bpmnError.getMessage()).isEqualTo("error occurred") + assertThat(bpmnError.getErrorCode()).isEqualTo("9999") + } + + @Test + void executeExternalAPIPostCall_shouldHandleExceptionsThrownByPostCall_andRethrowAsBpmnError() { + // GIVEN + HttpClient httpClient = mock(HttpClient.class) + willThrow(new RuntimeException("error occurred")).given(httpClient).post(BODY_PAYLOAD) + HttpClientFactory httpClientFactory = mock(HttpClientFactory.class) + given(httpClientFactory.create(new URL(URL), MediaType.APPLICATION_JSON, TargetEntity.AAI)).willReturn(httpClient) + DelegateExecution delegateExecution = createDelegateExecution() + DummyExceptionUtil exceptionUtil = new DummyExceptionUtil() + + // WHEN + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil(httpClientFactory, new DummyMsoUtils(UUID_STR), exceptionUtil) + BpmnError bpmnError = catchThrowableOfType({ -> + externalAPIUtil.executeExternalAPIPostCall(delegateExecution, URL, BODY_PAYLOAD) + }, BpmnError.class) + + // THEN + assertThat(exceptionUtil.getDelegateExecution()).isSameAs(delegateExecution) + assertThat(bpmnError.getMessage()).isEqualTo("error occurred") + assertThat(bpmnError.getErrorCode()).isEqualTo("9999") + } + + @Test + void executeExternalAPIPostCall_shouldPerformRestPostCall_withPayloadAndAuthorizationHeaderSet() { + // GIVEN + Response expectedResponse = createExpectedResponse(HttpStatus.ACCEPTED, BODY_PAYLOAD) + HttpClient httpClient = mock(HttpClient.class) + given(httpClient.post(BODY_PAYLOAD)).willReturn(expectedResponse) + HttpClientFactory httpClientFactory = mock(HttpClientFactory.class) + given(httpClientFactory.create(new URL(URL), MediaType.APPLICATION_JSON, TargetEntity.AAI)).willReturn(httpClient) + + // WHEN + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil(httpClientFactory, new DummyMsoUtils(UUID_STR), new ExceptionUtil()) + Response apiResponse = externalAPIUtil.executeExternalAPIPostCall(createDelegateExecution(), URL, BODY_PAYLOAD) + + // THEN + then(httpClient).should(times(1)).addBasicAuthHeader("value_externalapi_auth", "value_mso_msoKey") + then(httpClient).should(times(1)).addAdditionalHeader("X-FromAppId", "MSO") + then(httpClient).should(times(1)).addAdditionalHeader("X-TransactionId", UUID_STR) + ResponseAssert.assertThat(apiResponse) + .hasStatusCode(HttpStatus.ACCEPTED) + .hasBody(BODY_PAYLOAD) + } + + private Response createExpectedResponse(HttpStatus httpStatus, String body) { + Response expectedResponse = mock(Response.class) + given(expectedResponse.getStatus()).willReturn(httpStatus.value()) + given(expectedResponse.getEntity()).willReturn(body) + return expectedResponse + } + + private DelegateExecution createDelegateExecution() { + DelegateExecution delegateExecution = mock(DelegateExecution.class) + given(delegateExecution.getVariable("URN_externalapi_auth")).willReturn("value_externalapi_auth") + given(delegateExecution.getVariable("URN_mso_msoKey")).willReturn("value_mso_msoKey") + return delegateExecution + } + + private static class ResponseAssert extends AbstractAssert<ResponseAssert, Response> { + + ResponseAssert(Response response) { + super(response, ResponseAssert.class) + } + + static ResponseAssert assertThat(Response response) { + return new ResponseAssert(response) + } + + ResponseAssert hasStatusCode(HttpStatus httpStatus) { + assertThat(actual.getStatus()).isEqualTo(httpStatus.value()) + return this + } + + ResponseAssert hasBody(String responseBody) { + assertThat(actual.getEntity()).isEqualTo(responseBody) + return this + } + } + + private static class DummyMsoUtils extends MsoUtils { + + private final String uuid + + DummyMsoUtils(String uuid) { + this.uuid = uuid + } + + String getRequestID() { + return uuid + } + } + + private static class DummyExceptionUtil extends ExceptionUtil { + + private DelegateExecution delegateExecution + + @Override + void buildAndThrowWorkflowException(DelegateExecution delegateExecution, int errorCode, String errorMessage) { + this.delegateExecution = delegateExecution + throw new BpmnError(String.valueOf(errorCode), errorMessage) + } + + DelegateExecution getDelegateExecution() { + return delegateExecution + } + } +}
\ No newline at end of file diff --git a/bpmn/mso-infrastructure-bpmn/pom.xml b/bpmn/mso-infrastructure-bpmn/pom.xml index e3d611275e..f84d485437 100644 --- a/bpmn/mso-infrastructure-bpmn/pom.xml +++ b/bpmn/mso-infrastructure-bpmn/pom.xml @@ -220,12 +220,12 @@ <artifactId>camunda-bpm-assert</artifactId> <version>2.0-alpha2</version> <scope>test</scope> - </dependency> + </dependency> <dependency> - <groupId>org.assertj</groupId> - <artifactId>assertj-core</artifactId> - <version>1.7.0</version> - <scope>test</scope> - </dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>1.7.0</version> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/bpmn/pom.xml b/bpmn/pom.xml index a7b49bc52a..c66178b182 100644 --- a/bpmn/pom.xml +++ b/bpmn/pom.xml @@ -64,7 +64,7 @@ <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> - <version>1.7.0</version> + <version>3.11.1</version> <scope>test</scope> </dependency> </dependencies> diff --git a/bpmn/so-bpmn-building-blocks/pom.xml b/bpmn/so-bpmn-building-blocks/pom.xml index 8f1f2bf56e..4fe02ee3ac 100644 --- a/bpmn/so-bpmn-building-blocks/pom.xml +++ b/bpmn/so-bpmn-building-blocks/pom.xml @@ -107,6 +107,12 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>1.7.0</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> diff --git a/bpmn/so-bpmn-infrastructure-common/src/main/groovy/org/onap/so/bpmn/infrastructure/scripts/Create3rdONAPE2EServiceInstance.groovy b/bpmn/so-bpmn-infrastructure-common/src/main/groovy/org/onap/so/bpmn/infrastructure/scripts/Create3rdONAPE2EServiceInstance.groovy index 2ae7686703..05fd517e47 100644 --- a/bpmn/so-bpmn-infrastructure-common/src/main/groovy/org/onap/so/bpmn/infrastructure/scripts/Create3rdONAPE2EServiceInstance.groovy +++ b/bpmn/so-bpmn-infrastructure-common/src/main/groovy/org/onap/so/bpmn/infrastructure/scripts/Create3rdONAPE2EServiceInstance.groovy @@ -23,6 +23,7 @@ package org.onap.so.bpmn.infrastructure.scripts import org.json.JSONArray import org.json.JSONObject import org.json.XML +import org.onap.so.bpmn.common.scripts.ExternalAPIUtilFactory import static org.apache.commons.lang3.StringUtils.* import groovy.xml.XmlUtil @@ -381,7 +382,7 @@ public class Create3rdONAPE2EServiceInstance extends AbstractServiceTaskProcesso valueMap.put("serviceName", '"' + serviceName + '"') valueMap.put("serviceUuId", '"' + serviceUuId + '"') - ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil() + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtilFactory().create() // insert CallSource='ExternalAPI' to uuiRequest Map<String, String> requestInputsMap = new HashMap<>() @@ -426,7 +427,7 @@ public class Create3rdONAPE2EServiceInstance extends AbstractServiceTaskProcesso msoLogger.debug("doCreateE2ESIin3rdONAP externalAPIURL is: " + extAPIPath) msoLogger.debug("doCreateE2ESIin3rdONAP payload is: " + payload) - ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil() + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtilFactory().create() execution.setVariable("ServiceOrderId", "") Response response = externalAPIUtil.executeExternalAPIPostCall(execution, extAPIPath, payload) @@ -471,7 +472,7 @@ public class Create3rdONAPE2EServiceInstance extends AbstractServiceTaskProcesso extAPIPath += "/" + execution.getVariable("ServiceOrderId") msoLogger.debug("getE2ESIProgressin3rdONAP create externalAPIURL is: " + extAPIPath) - ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil() + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtilFactory().create() Response response = externalAPIUtil.executeExternalAPIGetCall(execution, extAPIPath) diff --git a/bpmn/so-bpmn-infrastructure-common/src/main/groovy/org/onap/so/bpmn/infrastructure/scripts/Delete3rdONAPE2EServiceInstance.groovy b/bpmn/so-bpmn-infrastructure-common/src/main/groovy/org/onap/so/bpmn/infrastructure/scripts/Delete3rdONAPE2EServiceInstance.groovy index 078d68bd75..b718e4a2e0 100644 --- a/bpmn/so-bpmn-infrastructure-common/src/main/groovy/org/onap/so/bpmn/infrastructure/scripts/Delete3rdONAPE2EServiceInstance.groovy +++ b/bpmn/so-bpmn-infrastructure-common/src/main/groovy/org/onap/so/bpmn/infrastructure/scripts/Delete3rdONAPE2EServiceInstance.groovy @@ -20,6 +20,8 @@ package org.onap.so.bpmn.infrastructure.scripts +import org.onap.so.bpmn.common.scripts.ExternalAPIUtilFactory + import javax.ws.rs.NotFoundException import javax.ws.rs.core.Response @@ -249,7 +251,7 @@ public class Delete3rdONAPE2EServiceInstance extends AbstractServiceTaskProcesso valueMap.put("serviceName", "null") valueMap.put("serviceUuId", '"' + serviceSpecificationId + '"') - ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil() + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtilFactory().create() valueMap.put("_requestInputs_", "") @@ -271,7 +273,7 @@ public class Delete3rdONAPE2EServiceInstance extends AbstractServiceTaskProcesso String extAPIPath = sppartnerUrl + "/service?relatedParty.id=" + globalSubscriberId msoLogger.debug("queryServicefrom3rdONAP externalAPIURL is: " + extAPIPath) - ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil() + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtilFactory().create() Response response = externalAPIUtil.executeExternalAPIGetCall(execution, extAPIPath) @@ -317,7 +319,7 @@ public class Delete3rdONAPE2EServiceInstance extends AbstractServiceTaskProcesso msoLogger.debug("doDeleteE2ESIin3rdONAP externalAPIURL is: " + extAPIPath) msoLogger.debug("doDeleteE2ESIin3rdONAP payload is: " + payload) - ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil() + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtilFactory().create() execution.setVariable("ServiceOrderId", "") Response response = externalAPIUtil.executeExternalAPIPostCall(execution, extAPIPath, payload) @@ -361,7 +363,7 @@ public class Delete3rdONAPE2EServiceInstance extends AbstractServiceTaskProcesso extAPIPath += "/" + execution.getVariable("ServiceOrderId") msoLogger.debug("getE2ESIProgressin3rdONAP delete externalAPIURL is: " + extAPIPath) - ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil() + ExternalAPIUtil externalAPIUtil = new ExternalAPIUtilFactory().create() Response response = externalAPIUtil.executeExternalAPIGetCall(execution, extAPIPath) diff --git a/common/src/main/java/org/onap/so/client/HttpClientFactory.java b/common/src/main/java/org/onap/so/client/HttpClientFactory.java new file mode 100644 index 0000000000..9705d7d0c4 --- /dev/null +++ b/common/src/main/java/org/onap/so/client/HttpClientFactory.java @@ -0,0 +1,30 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - SO + * ================================================================================ + * Copyright (C) 2018 Nokia. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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. + * ============LICENSE_END========================================================= + */ +package org.onap.so.client; + +import java.net.URL; +import org.onap.so.utils.TargetEntity; + +public class HttpClientFactory { + + public HttpClient create(URL host, String contentType, TargetEntity targetEntity) { + return new HttpClient(host, contentType, targetEntity); + } +} @@ -641,6 +641,7 @@ <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> + <version>3.11.1</version> <scope>test</scope> </dependency> <dependency> |