diff options
author | 2024-05-27 15:02:35 +0100 | |
---|---|---|
committer | 2024-05-29 17:27:26 +0100 | |
commit | 7cb64300ddd10a9e9f0270a8c4832263e20d8ad3 (patch) | |
tree | ab1d6ae63220aa1c1c76e2c9f0e7bae75100cb5e /cps-ncmp-service/src | |
parent | c97bed8797bf9f16a019d8fdf25e2191b6854136 (diff) |
Simplify Code around building DMI URLs
- Bug analysis showed some unclear and complicated code for building URLs
- This patch is to clean up the related code and show more clearly what is encoded and what is not
- Use SPringBoot provided builder to apply correct encoding of all URI parts
- DmiOperations base class no longer need as no common code model and data operations anymore
- Converted builder to POJO (client needs to provide the only dmi properties upon build)
- Added some missing test (in affected code) to increase coverage percntage
(as the production code has significantly reduced in size the overal coverage was inadvertently reduced below the treshold)
- Added more unit test. Now impl.operations' package has 100%
- removed the test fro Dmi Exception that was incorrectly callign the handleException methdod directly instead of mocking an exception
it was really a dupclicate of a test I added but I was able to re-use the event checkign in my test
Issue-ID: CPS-2246
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Change-Id: I1ff196cd03bbb95f5877ee590c321febeb672800
Diffstat (limited to 'cps-ncmp-service/src')
14 files changed, 417 insertions, 392 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index 49894dedb..637087909 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NcmpResponseStatus; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; @@ -45,7 +46,7 @@ import org.onap.cps.ncmp.api.models.DataOperationRequest; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; @@ -53,17 +54,15 @@ import org.springframework.web.util.UriComponentsBuilder; /** * Operations class for DMI data. */ -@Component +@RequiredArgsConstructor +@Service @Slf4j -public class DmiDataOperations extends DmiOperations { +public class DmiDataOperations { - public DmiDataOperations(final InventoryPersistence inventoryPersistence, - final JsonObjectMapper jsonObjectMapper, - final DmiProperties dmiProperties, - final DmiRestClient dmiRestClient, - final DmiServiceUrlBuilder dmiServiceUrlBuilder) { - super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); - } + private final InventoryPersistence inventoryPersistence; + private final JsonObjectMapper jsonObjectMapper; + private final DmiProperties dmiProperties; + private final DmiRestClient dmiRestClient; /** * This method fetches the resource data from operational data store for given cm handle @@ -89,39 +88,33 @@ public class DmiDataOperations extends DmiOperations { validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); - final MultiValueMap<String, String> uriQueryParamsMap = getUriQueryParamsMap( - cmResourceAddress.resourceIdentifier(), optionsParamInQuery, topicParamInQuery); - final Map<String, Object> uriVariableParamsMap = getUriVariableParamsMap(cmResourceAddress.datastoreName(), - yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), cmResourceAddress.cmHandleId()); - final String dmiResourceDataUrl = getDmiRequestUrl(uriQueryParamsMap, uriVariableParamsMap); - - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, authorization); + final String dmiUrl = getDmiResourceDataUrl(cmResourceAddress.datastoreName(), + yangModelCmHandle, + cmResourceAddress.resourceIdentifier(), + optionsParamInQuery, + topicParamInQuery); + return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, READ, authorization); } /** * This method fetches all the resource data from operational data store for given cm handle * identifier using dmi client. * - * @param dataStoreName data store name + * @param datastoreName data store name * @param cmHandleId network resource identifier * @param requestId requestId for async responses * @return {@code ResponseEntity} response entity */ - public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName, + public ResponseEntity<Object> getResourceDataFromDmi(final String datastoreName, final String cmHandleId, final String requestId) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); - final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, - yangModelCmHandle); - - final MultiValueMap<String, String> uriQueryParamsMap = getUriQueryParamsMap("/", null, null); - final Map<String, Object> uriVariableParamsMap = getUriVariableParamsMap(dataStoreName, - yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), cmHandleId); - final String dmiResourceDataUrl = getDmiRequestUrl(uriQueryParamsMap, uriVariableParamsMap); - final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, null); + + final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); + final String dmiUrl = getDmiResourceDataUrl(datastoreName, yangModelCmHandle, "/", null, null); + return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, READ, null); } /** @@ -142,13 +135,13 @@ public class DmiDataOperations extends DmiOperations { = getDistinctCmHandleIdsFromDataOperationRequest(dataOperationRequest); final Collection<YangModelCmHandle> yangModelCmHandles - = inventoryPersistence.getYangModelCmHandles(cmHandlesIds); + = inventoryPersistence.getYangModelCmHandles(cmHandlesIds); final Map<String, List<DmiDataOperation>> operationsOutPerDmiServiceName = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(topicParamInQuery, requestId, dataOperationRequest, yangModelCmHandles); - buildDataOperationRequestUrlAndSendToDmiService(topicParamInQuery, requestId, operationsOutPerDmiServiceName, + buildDataOperationRequestUrlAndSendToDmiService(requestId, topicParamInQuery, operationsOutPerDmiServiceName, authorization); } @@ -171,16 +164,13 @@ public class DmiDataOperations extends DmiOperations { final String dataType, final String authorization) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); - final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType, - yangModelCmHandle); - - final MultiValueMap<String, String> uriQueryParamsMap = getUriQueryParamsMap(resourceId, null, null); - final Map<String, Object> uriVariableParamsMap = getUriVariableParamsMap(PASSTHROUGH_RUNNING.getDatastoreName(), - yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), cmHandleId); - final String dmiUrl = getDmiRequestUrl(uriQueryParamsMap, uriVariableParamsMap); - final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); + + final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType, + yangModelCmHandle); + final String dmiUrl = getDmiResourceDataUrl(PASSTHROUGH_RUNNING.getDatastoreName(), + yangModelCmHandle, resourceId, null, null); return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operationType, authorization); } @@ -204,31 +194,22 @@ public class DmiDataOperations extends DmiOperations { return jsonObjectMapper.asJsonString(dmiRequestBody); } - private String getDmiRequestUrl(final MultiValueMap<String, String> uriQueryParamsMap, - final Map<String, Object> uriVariableParamsMap) { - return dmiServiceUrlBuilder.getDmiDatastoreUrl(uriQueryParamsMap, uriVariableParamsMap); - } - - private MultiValueMap<String, String> getUriQueryParamsMap(final String resourceId, - final String optionsParamInQuery, - final String topicParamInQuery) { - return dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, - topicParamInQuery); - } - - private Map<String, Object> getUriVariableParamsMap(final String dataStoreName, - final String dmiServiceName, - final String cmHandleId) { - return dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName, cmHandleId); - } - - private String getDmiServiceDataOperationRequestUrl(final String dmiServiceName, - final String topicParamInQuery, - final String requestId) { - final MultiValueMap<String, String> dataOperationRequestQueryParams = dmiServiceUrlBuilder - .getDataOperationRequestQueryParams(topicParamInQuery, requestId); - return dmiServiceUrlBuilder.getDataOperationRequestUrl(dataOperationRequestQueryParams, - dmiServiceUrlBuilder.populateDataOperationRequestUriVariables(dmiServiceName)); + private String getDmiResourceDataUrl(final String datastoreName, + final YangModelCmHandle yangModelCmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery) { + final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA); + return DmiServiceUrlBuilder.newInstance() + .pathSegment("ch") + .variablePathSegment("cmHandleId", yangModelCmHandle.getId()) + .pathSegment("data") + .pathSegment("ds") + .variablePathSegment("datastore", datastoreName) + .queryParameter("resourceIdentifier", resourceIdentifier) + .queryParameter("options", optionsParamInQuery) + .queryParameter("topic", topicParamInQuery) + .build(dmiServiceName, dmiProperties.getDmiBasePath()); } private void validateIfCmHandleStateReady(final YangModelCmHandle yangModelCmHandle, @@ -247,32 +228,32 @@ public class DmiDataOperations extends DmiOperations { dataOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet()); } - private void buildDataOperationRequestUrlAndSendToDmiService(final String topicParamInQuery, - final String requestId, + private void buildDataOperationRequestUrlAndSendToDmiService(final String requestId, + final String topicParamInQuery, final Map<String, List<DmiDataOperation>> groupsOutPerDmiServiceName, final String authorization) { groupsOutPerDmiServiceName.forEach((dmiServiceName, dmiDataOperationRequestBodies) -> { - final String dmiDataOperationResourceUrl = - getDmiServiceDataOperationRequestUrl(dmiServiceName, topicParamInQuery, requestId); - sendDataOperationRequestToDmiService(dmiDataOperationResourceUrl, dmiDataOperationRequestBodies, - authorization); + final String dmiUrl = DmiServiceUrlBuilder.newInstance() + .pathSegment("data") + .queryParameter("requestId", requestId) + .queryParameter("topic", topicParamInQuery) + .build(dmiServiceName, dmiProperties.getDmiBasePath()); + sendDataOperationRequestToDmiService(dmiUrl, dmiDataOperationRequestBodies, authorization); }); } - private void sendDataOperationRequestToDmiService(final String dataOperationResourceUrl, + private void sendDataOperationRequestToDmiService(final String dmiUrl, final List<DmiDataOperation> dmiDataOperationRequestBodies, final String authorization) { final DmiDataOperationRequest dmiDataOperationRequest = DmiDataOperationRequest.builder() .operations(dmiDataOperationRequestBodies).build(); - final String dmiDataOperationRequestAsJsonString = - jsonObjectMapper.asJsonString(dmiDataOperationRequest); + final String dmiDataOperationRequestAsJsonString = jsonObjectMapper.asJsonString(dmiDataOperationRequest); try { - dmiRestClient.postOperationWithJsonData(dataOperationResourceUrl, dmiDataOperationRequestAsJsonString, READ, - authorization); + dmiRestClient.postOperationWithJsonData(dmiUrl, dmiDataOperationRequestAsJsonString, READ, authorization); } catch (final DmiClientRequestException e) { - handleTaskCompletionException(e, dataOperationResourceUrl, dmiDataOperationRequestBodies); + handleTaskCompletionException(e, dmiUrl, dmiDataOperationRequestBodies); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java index d54dcb8de..78d27b54b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java @@ -33,34 +33,27 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.DmiProperties; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.models.YangResource; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; /** * Operations class for DMI Model. */ -@Component -public class DmiModelOperations extends DmiOperations { +@RequiredArgsConstructor +@Service +public class DmiModelOperations { - /** - * Constructor for {@code DmiOperations}. This method also manipulates url properties. - * - * @param dmiRestClient {@code DmiRestClient} - */ - public DmiModelOperations(final InventoryPersistence inventoryPersistence, - final JsonObjectMapper jsonObjectMapper, - final DmiProperties dmiProperties, - final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) { - super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); - } + private final JsonObjectMapper jsonObjectMapper; + private final DmiProperties dmiProperties; + private final DmiRestClient dmiRestClient; /** * Retrieves module references. @@ -113,9 +106,12 @@ public class DmiModelOperations extends DmiOperations { final String jsonRequestBody, final String cmHandle, final String resourceName) { - final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, - OperationType.READ, null); + final String dmiUrl = DmiServiceUrlBuilder.newInstance() + .pathSegment("ch") + .variablePathSegment("cmHandleId", cmHandle) + .variablePathSegment("resourceName", resourceName) + .build(dmiServiceName, dmiProperties.getDmiBasePath()); + return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, OperationType.READ, null); } private static String getRequestBodyToFetchYangResources(final Collection<ModuleReference> newModuleReferences, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java deleted file mode 100644 index c195ab309..000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.operations; - -import lombok.RequiredArgsConstructor; -import org.onap.cps.ncmp.api.impl.client.DmiRestClient; -import org.onap.cps.ncmp.api.impl.config.DmiProperties; -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; -import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.stereotype.Service; - -@RequiredArgsConstructor -@Service -public class DmiOperations { - - protected final InventoryPersistence inventoryPersistence; - protected final JsonObjectMapper jsonObjectMapper; - protected final DmiProperties dmiProperties; - protected final DmiRestClient dmiRestClient; - protected final DmiServiceUrlBuilder dmiServiceUrlBuilder; - - String getDmiResourceUrl(final String dmiServiceName, final String cmHandle, final String resourceName) { - return dmiServiceUrlBuilder.getResourceDataBasePathUriBuilder() - .pathSegment("{resourceName}") - .buildAndExpand(dmiServiceName, dmiProperties.getDmiBasePath(), cmHandle, resourceName).toUriString(); - } -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java index fa00d1a15..e863228ed 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java @@ -21,9 +21,7 @@ package org.onap.cps.ncmp.api.impl.operations; import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.Locale; import lombok.Getter; import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException; @@ -48,13 +46,6 @@ public enum OperationType { return String.valueOf(operationName); } - private static final Map<String, OperationType> operationNameToOperationEnum = new HashMap<>(); - - static { - Arrays.stream(OperationType.values()).forEach( - operationType -> operationNameToOperationEnum.put(operationType.getOperationName(), operationType)); - } - /** * From operation name get operation enum type. * @@ -62,10 +53,10 @@ public enum OperationType { * @return the operation enum type */ public static OperationType fromOperationName(final String operationName) { - final OperationType operationType = operationNameToOperationEnum.get(operationName); - if (null == operationType) { + try { + return OperationType.valueOf(operationName.toUpperCase(Locale.ENGLISH)); + } catch (final IllegalArgumentException e) { throw new InvalidOperationException(operationName + " is an invalid operation name"); } - return operationType; } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java index 15f1effdd..aeeeb6430 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java @@ -20,167 +20,92 @@ package org.onap.cps.ncmp.api.impl.utils; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; -import lombok.RequiredArgsConstructor; +import lombok.NoArgsConstructor; import org.apache.logging.log4j.util.Strings; -import org.apache.logging.log4j.util.TriConsumer; -import org.onap.cps.ncmp.api.impl.config.DmiProperties; -import org.onap.cps.spi.utils.CpsValidator; -import org.springframework.stereotype.Component; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; -@Component -@RequiredArgsConstructor +@NoArgsConstructor public class DmiServiceUrlBuilder { - private final DmiProperties dmiProperties; - private final CpsValidator cpsValidator; - /** - * This method creates the dmi service url. - * - * @param queryParams query param map as key,value pair - * @param uriVariables uri param map as key (placeholder),value pair - * @return {@code String} dmi service url as string - */ - public String getDmiDatastoreUrl(final MultiValueMap<String, String> queryParams, - final Map<String, Object> uriVariables) { - return getUriComponentsBuilder(getResourceDataBasePathUriBuilder(), queryParams, uriVariables) - .buildAndExpand().toUriString(); - } + private static final String FIXED_PATH_SEGMENT = null; - /** - * This method builds data operation request url. - * - * @param dataoperationRequestQueryParams query param map as key, value pair - * @param dataoperationRequestUriVariables uri param map as key (placeholder), value pair - * @return {@code String} data operation request url as string - */ - public String getDataOperationRequestUrl(final MultiValueMap<String, String> dataoperationRequestQueryParams, - final Map<String, Object> dataoperationRequestUriVariables) { - return getDataOperationResourceDataBasePathUriBuilder() - .queryParams(dataoperationRequestQueryParams) - .uriVariables(dataoperationRequestUriVariables) - .buildAndExpand().toUriString(); - } + final UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance(); + final Map<String, Object> pathSegments = new LinkedHashMap<>(); - /** - * This method creates the dmi service url builder object with path variables. - * - * @return {@code UriComponentsBuilder} dmi service url builder object - */ - public UriComponentsBuilder getResourceDataBasePathUriBuilder() { - return UriComponentsBuilder.newInstance() - .path("{dmiServiceName}") - .pathSegment("{dmiBasePath}") - .pathSegment("v1") - .pathSegment("ch") - .pathSegment("{cmHandleId}"); + public static DmiServiceUrlBuilder newInstance() { + return new DmiServiceUrlBuilder(); } /** - * This method creates the dmi service url builder object with path variables for data operation request. + * Add a fixed pathSegment to the URI. * - * @return {@code UriComponentsBuilder} dmi service url builder object + * @param pathSegment the path segment + * @return this builder */ - public UriComponentsBuilder getDataOperationResourceDataBasePathUriBuilder() { - return UriComponentsBuilder.newInstance() - .path("{dmiServiceName}") - .pathSegment("{dmiBasePath}") - .pathSegment("v1") - .pathSegment("data"); + public DmiServiceUrlBuilder pathSegment(final String pathSegment) { + pathSegments.put(pathSegment, FIXED_PATH_SEGMENT); + return this; } /** - * This method populates uri variables. + * Add a variable pathSegment to the URI. + * Do NOT add { } braces. the builder will take care of that * - * @param dataStoreName data store name - * @param dmiServiceName dmi service name - * @param cmHandleId cm handle id for dmi registration - * @return {@code String} dmi service url as string + * @param pathSegment the name of the variable path segment (with { and } + * @param value the value to be insert in teh URI for the given variable path segment + * @return this builder */ - public Map<String, Object> populateUriVariables(final String dataStoreName, - final String dmiServiceName, - final String cmHandleId) { - cpsValidator.validateNameCharacters(cmHandleId); - final Map<String, Object> uriVariables = new HashMap<>(); - final String dmiBasePath = dmiProperties.getDmiBasePath(); - uriVariables.put("dmiServiceName", dmiServiceName); - uriVariables.put("dmiBasePath", dmiBasePath); - uriVariables.put("cmHandleId", cmHandleId); - uriVariables.put("dataStore", dataStoreName); - return uriVariables; + public DmiServiceUrlBuilder variablePathSegment(final String pathSegment, final Object value) { + pathSegments.put(pathSegment, value); + return this; } /** - * This method populates uri variables for data operation request. + * Add a query parameter to the URI. + * Do NOT encode as the builder wil take care of encoding * - * @param dmiServiceName dmi service name - * @return {@code Map<String, Object>} uri variables as map - */ - public Map<String, Object> populateDataOperationRequestUriVariables(final String dmiServiceName) { - final Map<String, Object> uriVariables = new HashMap<>(); - final String dmiBasePath = dmiProperties.getDmiBasePath(); - uriVariables.put("dmiServiceName", dmiServiceName); - uriVariables.put("dmiBasePath", dmiBasePath); - return uriVariables; - } - - /** - * This method is used to populate map from query params. + * @param name the name of the variable + * @param value the value of the variable (only Strings are supported). * - * @param resourceId unique id of response for valid topic - * @param optionsParamInQuery options as provided by client - * @param topicParamInQuery topic as provided by client - * @return all valid query params as map + * @return this builder */ - public MultiValueMap<String, String> populateQueryParams(final String resourceId, - final String optionsParamInQuery, - final String topicParamInQuery) { - final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); - getQueryParamConsumer().accept("resourceIdentifier", resourceId, queryParams); - getQueryParamConsumer().accept("options", optionsParamInQuery, queryParams); - if (Strings.isNotEmpty(topicParamInQuery)) { - getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams); + public DmiServiceUrlBuilder queryParameter(final String name, final String value) { + if (Strings.isNotBlank(value)) { + uriComponentsBuilder.queryParam(name, value); } - return queryParams; + return this; } /** - * This method is used to populate map from query params for data operation request. + * Build the URI as a correctly percentage-encoded String. * - * @param topicParamInQuery topic into url param - * @param requestId unique id of response for valid topic - * @return all valid query params as map + * @param dmiServiceName the name of the dmi service + * @param dmiBasePath the base path of the dmi service + * + * @return URI as a string */ - public MultiValueMap<String, String> getDataOperationRequestQueryParams(final String topicParamInQuery, - final String requestId) { - final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); - getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams); - getQueryParamConsumer().accept("requestId", requestId, queryParams); - return queryParams; - } + public String build(final String dmiServiceName, final String dmiBasePath) { + uriComponentsBuilder + .path("{dmiServiceName}") + .pathSegment("{dmiBasePath}") + .pathSegment("v1"); - private TriConsumer<String, String, MultiValueMap<String, String>> getQueryParamConsumer() { - return (paramName, paramValue, paramMap) -> { - if (Strings.isNotEmpty(paramValue)) { - paramMap.add(paramName, URLEncoder.encode(paramValue, StandardCharsets.UTF_8)); + final Map<String, Object> uriVariables = new HashMap<>(); + uriVariables.put("dmiServiceName", dmiServiceName); + uriVariables.put("dmiBasePath", dmiBasePath); + + pathSegments.forEach((pathSegment, variablePathValue) -> { + if (variablePathValue == FIXED_PATH_SEGMENT) { + uriComponentsBuilder.pathSegment(pathSegment); + } else { + uriComponentsBuilder.pathSegment("{" + pathSegment + "}"); + uriVariables.put(pathSegment, variablePathValue); } - }; + }); + return uriComponentsBuilder.buildAndExpand(uriVariables).encode().toUriString(); } - private UriComponentsBuilder getUriComponentsBuilder(final UriComponentsBuilder uriComponentsBuilder, - final MultiValueMap<String, String> queryParams, - final Map<String, Object> uriVariables) { - return uriComponentsBuilder - .pathSegment("data") - .pathSegment("ds") - .pathSegment("{dataStore}") - .queryParams(queryParams) - .uriVariables(uriVariables); - } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy new file mode 100644 index 000000000..c763c522c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiPropertiesSpec.groovy @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config + +import spock.lang.Specification + +class DmiPropertiesSpec extends Specification { + + def objectUnderTest = new DmiProperties() + + def 'Geting dmi base path.'() { + given: 'base path of #dmiBasePath' + objectUnderTest.dmiBasePath = dmiBasePath + expect: 'Preceding and trailing slash wil be removed' + assert objectUnderTest.getDmiBasePath() == 'test' + where: 'the following dmi base paths are used' + dmiBasePath << [ 'test' , '/test', 'test/', '/test/' ] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy new file mode 100644 index 000000000..7e364c97c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.operations + +import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException +import spock.lang.Specification + +class DatastoreTypeSpec extends Specification { + + def 'Converting string to enum.'() { + expect: 'converting string to enum results in the correct enum value' + DatastoreType.fromDatastoreName(datastoreName) == expectedEnum + where: 'the following datastore names are used' + datastoreName || expectedEnum + 'ncmp-datastore:operational' || DatastoreType.OPERATIONAL + 'ncmp-datastore:passthrough-running' || DatastoreType.PASSTHROUGH_RUNNING + 'ncmp-datastore:passthrough-operational' || DatastoreType.PASSTHROUGH_OPERATIONAL + } + + def 'Converting unknown name string to enum.'() { + when: 'attempt converting unknown datastore name' + DatastoreType.fromDatastoreName('unknown') + then: 'an invalid datastore exception is thrown' + def thrown = thrown(InvalidDatastoreException) + assert thrown.message.contains('unknown is an invalid datastore') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy index 9dafd9ed2..a84e1348e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy @@ -21,7 +21,9 @@ package org.onap.cps.ncmp.api.impl.operations -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA +import org.onap.cps.ncmp.api.impl.inventory.CmHandleState + +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING @@ -33,7 +35,6 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.events.EventsPublisher import org.onap.cps.ncmp.api.impl.config.DmiProperties import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException -import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.api.models.CmResourceAddress @@ -52,12 +53,11 @@ import spock.lang.Shared @ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations]) class DmiDataOperationsSpec extends DmiOperationsBaseSpec { - @SpringBean - DmiServiceUrlBuilder dmiServiceUrlBuilder = Mock() def dmiServiceBaseUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds/ncmp-datastore:" def NO_TOPIC = null def NO_REQUEST_ID = null def NO_AUTH_HEADER = null + @Shared def OPTIONS_PARAM = '(a=1,b=2)' @@ -75,22 +75,22 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { mockYangModelCmHandleRetrieval(dmiProperties) and: 'a positive response from DMI service when it is called with the expected parameters' def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) - def expectedUrl = dmiServiceBaseUrl + "${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}" + def expectedUrl = "${dmiServiceBaseUrl}${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}" + def expectedJson = '{"operation":"read","cmHandleProperties":' + expectedProperties + ',"moduleSetTag":""}' mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl when: 'get resource data is invoked' def cmResourceAddress = new CmResourceAddress(dataStore.datastoreName, cmHandleId, resourceIdentifier) def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) then: 'the result is the response from the DMI service' assert result == responseFromDmi where: 'the following parameters are used' - scenario | dmiProperties | dataStore | options || expectedJson | expectedDatastoreInUrl | expectedOptionsInUrl - 'without properties' | [] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{},"moduleSetTag":""}' | 'passthrough-operational' | '&options=(a=1,b=2)' - 'with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' | 'passthrough-operational' | '&options=(a=1,b=2)' - 'null options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null || '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' | 'passthrough-operational' | '' - 'empty options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '' || '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' | 'passthrough-operational' | '' - 'datastore running without properties' | [] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{},"moduleSetTag":""}' | 'passthrough-running' | '&options=(a=1,b=2)' - 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' | 'passthrough-running' | '&options=(a=1,b=2)' + scenario | dmiProperties | dataStore | options || expectedProperties | expectedDatastoreInUrl | expectedOptionsInUrl + 'without properties' | [] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' + 'with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' + 'null options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null || '{"prop1":"val1"}' | 'passthrough-operational' | '' + 'empty options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '' || '{"prop1":"val1"}' | 'passthrough-operational' | '' + 'datastore running without properties' | [] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' + 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' } def 'Execute (async) data operation from DMI service.'() { @@ -101,43 +101,45 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] and: 'a positive response from DMI service when it is called with valid request parameters' def responseFromDmi = new ResponseEntity<Object>(HttpStatus.ACCEPTED) - def expectedDmiBatchResourceDataUrl = "ncmp/v1/data/topic=my-topic-name" + def expectedDmiBatchResourceDataUrl = "someServiceName/dmi/v1/data?requestId=requestId&topic=my-topic-name" def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}' mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ.operationName, NO_AUTH_HEADER) >> responseFromDmi - dmiServiceUrlBuilder.getDataOperationRequestUrl(_, _) >> expectedDmiBatchResourceDataUrl when: 'get resource data for group of cm handles are invoked' objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER) then: 'the post operation was called and ncmp generated dmi request body json args' 1 * mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) } - def 'Execute (async) data operation from DMI service with dmi client exception.'() { - given: 'data operation request body and dmi resource url' - def dmiDataOperation = DmiDataOperation.builder().operationId('some-operation-id').build() - dmiDataOperation.getCmHandles().add(DmiOperationCmHandle.builder().id('some-cm-handle-id').build()) - def dmiDataOperationResourceDataUrl = "http://dmi-service-name:dmi-port/dmi/v1/data?topic=my-topic-name&requestId=some-request-id" + def 'Execute (async) data operation from DMI service with Exception.'() { + given: 'collection of yang model cm Handles and data operation request' + mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) + def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) + dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] + and: 'the published cloud will be captured' def actualDataOperationCloudEvent = null - when: 'exception occurs after sending request to dmi service' - objectUnderTest.handleTaskCompletionException(new DmiClientRequestException(123, 'message', 'details', UNABLE_TO_READ_RESOURCE_DATA), dmiDataOperationResourceDataUrl, List.of(dmiDataOperation)) - then: 'a cloud event is published' - eventsPublisher.publishCloudEvent('my-topic-name', 'some-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } - and: 'the event contains the expected error details' + eventsPublisher.publishCloudEvent('my-topic-name', 'my-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } + and: 'a positive response from DMI service when it is called with valid request parameters' + mockDmiRestClient.postOperationWithJsonData(*_) >> { throw new DmiClientRequestException(123,'','', UNKNOWN_ERROR) } + when: 'attempt tp get resource data for group of cm handles are invoked' + objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'my-request-id', NO_AUTH_HEADER) + then: 'the event contains the expected error details' def eventDataValue = extractDataValue(actualDataOperationCloudEvent) - assert eventDataValue.operationId == dmiDataOperation.operationId - assert eventDataValue.ids == dmiDataOperation.cmHandles.id - assert eventDataValue.statusCode == '103' - assert eventDataValue.statusMessage == UNABLE_TO_READ_RESOURCE_DATA.message + assert eventDataValue.statusCode == '108' + assert eventDataValue.statusMessage == UNKNOWN_ERROR.message + and: 'the event contains the correct operation details' + assert eventDataValue.operationId == dataOperationRequest.dataOperationDefinitions[0].operationId + assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleIds } def 'call get all resource data.'() { given: 'the system returns a cm handle with a sample property and sample module set tag' - def sampleModuleSetTag = "mod-tag-1" - mockYangModelCmHandleRetrieval([yangModelCmHandleProperty], sampleModuleSetTag) + mockYangModelCmHandleRetrieval([yangModelCmHandleProperty], 'my-module-set-tag') and: 'a positive response from DMI service when it is called with the expected parameters' def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/" - mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"'+sampleModuleSetTag+'"}', READ, null) >> responseFromDmi - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl + def expectedJson = '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"my-module-set-tag"}' + mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, READ, null) >> responseFromDmi when: 'get resource data is invoked' def result = objectUnderTest.getResourceDataFromDmi( PASSTHROUGH_OPERATIONAL.datastoreName, cmHandleId, NO_REQUEST_ID) then: 'the result is the response from the DMI service' @@ -148,10 +150,9 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { given: 'a cm handle for #cmHandleId' mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) and: 'a positive response from DMI service when it is called with the expected parameters' - def expectedUrl = dmiServiceBaseUrl + "passthrough-running?resourceIdentifier=${resourceIdentifier}" + def expectedUrl = "${dmiServiceBaseUrl}passthrough-running?resourceIdentifier=${resourceIdentifier}" def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK) - dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi when: 'write resource method is invoked' def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER) @@ -163,7 +164,29 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { UPDATE || 'update' } + def 'State Ready validation'() { + given: ' a yang model cm handle' + populateYangModelCmHandle([] ,'') + when: 'Validating State of #cmHandleState' + def caughtException = null + try { + objectUnderTest.validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState) + } catch (Exception e) { + caughtException = e + } + then: 'only when not ready a exception is thrown' + if (expecteException) { + assert caughtException.details.contains('not in READY state') + } else { + assert caughtException == null + } + where: ' the following states are used' + cmHandleState || expecteException + CmHandleState.READY || false + CmHandleState.ADVISED || true + } + def extractDataValue(actualDataOperationCloudEvent) { - return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent.class).data.responses[0] + return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent).data.responses[0] } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy index 88af0479d..de5e15e50 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy @@ -58,8 +58,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']] def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules" def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK) - mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) - >> responseFromDmi + mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi when: 'get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) then: 'the result consists of expected module references' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy index 3518440ca..136ff7832 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy @@ -22,13 +22,10 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.impl.config.DmiProperties import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder import org.onap.cps.ncmp.api.impl.inventory.CmHandleState import org.onap.cps.ncmp.api.impl.inventory.CompositeState import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.spi.utils.CpsValidator import org.spockframework.spring.SpringBean import spock.lang.Shared import spock.lang.Specification @@ -44,16 +41,11 @@ abstract class DmiOperationsBaseSpec extends Specification { @SpringBean InventoryPersistence mockInventoryPersistence = Mock() - def mockCpsValidator = Mock(CpsValidator) - @SpringBean ObjectMapper spyObjectMapper = Spy() - @SpringBean - DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new DmiProperties(), mockCpsValidator) - def yangModelCmHandle = new YangModelCmHandle() - def static dmiServiceName = 'some service name' + def static dmiServiceName = 'someServiceName' def static cmHandleId = 'some-cm-handle' def static resourceIdentifier = 'parent/child' @@ -68,7 +60,7 @@ abstract class DmiOperationsBaseSpec extends Specification { } def mockYangModelCmHandleCollectionRetrieval(dmiProperties) { - populateYangModelCmHandle(dmiProperties, "") + populateYangModelCmHandle(dmiProperties, '') mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy new file mode 100644 index 000000000..d31b8d4fd --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy @@ -0,0 +1,48 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.operations + +import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException +import spock.lang.Specification + +class OperationTypeSpec extends Specification { + + def 'Converting string to enum.'() { + expect: 'converting string to enum results in the correct enum value' + OperationType.fromOperationName(operationName) == expectedEnum + where: 'the following datastore names are used' + operationName || expectedEnum + 'read' || OperationType.READ + 'create' || OperationType.CREATE + 'update' || OperationType.UPDATE + 'patch' || OperationType.PATCH + 'delete' || OperationType.DELETE + } + + def 'Converting unknown name string to enum.'() { + when: 'attempt converting unknown datastore name' + OperationType.fromOperationName('unknown') + then: 'an invalid operation exception is thrown' + def thrown = thrown(InvalidOperationException) + assert thrown.message.contains('unknown is an invalid operation') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy index 827f44850..69d08e3de 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy @@ -20,77 +20,67 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING - -import org.onap.cps.ncmp.api.impl.config.DmiProperties -import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService -import org.onap.cps.spi.utils.CpsValidator -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import spock.lang.Specification class DmiServiceUrlBuilderSpec extends Specification { - static YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName', - 'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id'),'my-module-set-tag', 'my-alternate-id', 'my-data-producer-identifier') - - DmiProperties dmiProperties = new DmiProperties() - - def mockCpsValidator = Mock(CpsValidator) + def objectUnderTest = new DmiServiceUrlBuilder() - def objectUnderTest = new DmiServiceUrlBuilder(dmiProperties, mockCpsValidator) - - def setup() { - dmiProperties.dmiBasePath = 'dmi' + def 'Build URI with (variable) path segments and parameters.'() { + given: 'the URI details are given to the builder' + objectUnderTest.pathSegment(segment1) + objectUnderTest.variablePathSegment('myVariableSegment','someValue') + objectUnderTest.pathSegment(segment2) + objectUnderTest.queryParameter('param1', paramValue1) + objectUnderTest.queryParameter('param2', paramValue2) + objectUnderTest.queryParameter('param3', null) + objectUnderTest.queryParameter('param4', '') + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'the URI is correct (segments are in correct order) ' + assert result == expectedUri + where: 'following URI details are used' + segment1 | segment2 | paramValue1 | paramValue2 || expectedUri + 'segment1' | 'segment2' | '123' | 'abc' || 'myDmiServer/myBasePath/v1/segment1/someValue/segment2?param1=123¶m2=abc' + 'segment2' | 'segment1' | 'abc' | '123' || 'myDmiServer/myBasePath/v1/segment2/someValue/segment1?param1=abc¶m2=123' } - def 'Create the dmi service url with #scenario.'() { - given: 'uri variables' - def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle') - and: 'query params' - def uriQueries = objectUnderTest.populateQueryParams(resourceId, 'optionsParamInQuery', topic) - when: 'a dmi datastore service url is generated' - def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars) - then: 'service url is generated as expected' - assert dmiServiceUrl == expectedDmiServiceUrl - where: 'the following parameters are used' - scenario | topic | resourceId || expectedDmiServiceUrl - 'With valid resourceId' | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty resourceId' | 'topicParamInQuery' | '' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty dmi base path' | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery' - 'With Empty topicParamInQuery' | '' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery' + def 'Build URI with special characters in path segments.'() { + given: 'the path segments are given to the builder' + objectUnderTest.pathSegment(segment) + objectUnderTest.variablePathSegment('myVariableSegment', variableSegmentValue) + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'Only teh characters that cause issues in path segments issues are encoded' + assert result == expectedUri + where: 'following variable path segments are used' + segment | variableSegmentValue || expectedUri + 'some/special?characters=are\\encoded' | 'my/variable/segment' || 'myDmiServer/myBasePath/v1/some%2Fspecial%3Fcharacters=are%5Cencoded/my%2Fvariable%2Fsegment' + 'but=some&are:not-!' | 'my&variable:segment' || 'myDmiServer/myBasePath/v1/but=some&are:not-!/my&variable:segment' } - def 'Populate dmi data store url #scenario.'() { - given: 'uri variables are created' - dmiProperties.dmiBasePath = dmiBasePath - def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle') - and: 'null query params' - def uriQueries = objectUnderTest.populateQueryParams(null, null, null) - when: 'a dmi datastore service url is generated' - def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars) - then: 'the created dmi service url matches the expected' - assert dmiServiceUrl == expectedDmiServiceUrl - where: 'the following parameters are used' - scenario | decription | dmiBasePath || expectedDmiServiceUrl - 'base path starts with /' | 'Remove / from start of base path' | '/dmi' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running' - 'base path ends with / ' | 'Remove / from end of base path' | 'dmi/' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running' - 'base path without any / ' | 'base path does not contains any /' | 'dmi' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running' + def 'Build URI with special characters in query parameters.'() { + given: 'the query parameter is given to the builder' + objectUnderTest.queryParameter(paramName, value) + when: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'Only the characters (in the name and value) that cause issues in query parameters are encoded' + assert result == expectedUri + where: 'the following query parameters are used' + paramName | value || expectedUri + 'my¶m' | 'some?special&characters=are\\encoded' || 'myDmiServer/myBasePath/v1?my%26param=some?special%26characters%3Dare%5Cencoded' + 'my-param' | 'but/some:are-not-!' || 'myDmiServer/myBasePath/v1?my-param=but/some:are-not-!' } - def 'Bath request Url creation.'() { - given: 'the required path parameters' - def batchRequestUriVariables = [dmiServiceName: 'some-service', dmiBasePath: 'testBase', cmHandleId: '123'] - and: 'the relevant query parameters' - def batchRequestQueryParams = objectUnderTest.getDataOperationRequestQueryParams('some topic', 'some id') - when: 'a URL is created' - def result = objectUnderTest.getDataOperationRequestUrl(batchRequestQueryParams, batchRequestUriVariables) - then: 'it is formed correctly' - assert result.toString() == 'some-service/testBase/v1/data?topic=some+topic&requestId=some+id' + def 'Build URI with empty query parameters.'() { + when: 'the query parameter is given to the builder' + objectUnderTest.queryParameter('param', value) + and: 'the URI (string) is build' + def result = objectUnderTest.build('myDmiServer', 'myBasePath') + then: 'no parameter gets added' + assert result == 'myDmiServer/myBasePath/v1' + where: 'the following parameter values are used' + value << [ null, '', ' ' ] } - def 'Populate batch uri variables.'() { - expect: 'Populate batch uri variables returns a map with given service name and base path from setup' - assert objectUnderTest.populateDataOperationRequestUriVariables('some service') == [dmiServiceName: 'some service', dmiBasePath: 'dmi' ] - } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy new file mode 100644 index 000000000..c72eb9e4c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/EventDateTimeFormatterSpec.groovy @@ -0,0 +1,45 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.utils + +import spock.lang.Specification +import java.time.Year + +class EventDateTimeFormatterSpec extends Specification { + + def 'Get ISO formatted date and time.' () { + expect: 'iso formatted date and time starts with current year' + assert EventDateTimeFormatter.getCurrentIsoFormattedDateTime().startsWith(String.valueOf(Year.now())) + } + + def 'Convert date time from string to OffsetDateTime type.'() { + when: 'date time as a string is converted to OffsetDateTime type' + def result = EventDateTimeFormatter.toIsoOffsetDateTime('2024-05-28T18:28:02.869+0100') + then: 'the result convert back back to a string is the same as the original timestamp (except the format of timezone offset)' + assert result.toString() == '2024-05-28T18:28:02.869+01:00' + } + + def 'Convert blank string.' () { + expect: 'converting a blank string result in null' + assert EventDateTimeFormatter.toIsoOffsetDateTime(' ') == null + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy index dc471e64f..54befb446 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy @@ -27,7 +27,6 @@ import spock.lang.Specification class RestQueryParametersValidatorSpec extends Specification { - def 'CM Handle Query validation: empty query.'() { given: 'a cm handle query' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() @@ -62,13 +61,13 @@ class RestQueryParametersValidatorSpec extends Specification { then: 'a data validation exception is thrown' def thrown = thrown(DataValidationException) and: 'the exception details contain the correct significant term ' - thrown.details.contains(expectedWordInDetails) + assert thrown.details.contains(expectedWordInDetails) where: scenario | conditionName | conditionParameters || expectedWordInDetails 'unknown condition name' | 'unknownCondition' | [['key': 'value']] || 'conditionName' 'no condition name' | '' | [['key': 'value']] || 'conditionName' + 'empty conditions' | 'validConditionName' | [] || 'conditionsParameters' 'empty properties' | 'validConditionName' | [[:]] || 'conditionsParameter' - 'empty conditions' | 'validConditionName' | [[:]] || 'conditionsParameter' 'too many properties' | 'validConditionName' | [[key1: 'value1', key2: 'value2']] || 'conditionsParameter' 'empty key' | 'validConditionName' | [['': 'wrong']] || 'conditionsParameter' } |