diff options
author | 2025-03-10 16:46:22 +0000 | |
---|---|---|
committer | 2025-03-13 10:06:38 +0000 | |
commit | 4f002a7da4001e2fc00d1ed37154112d9c13a828 (patch) | |
tree | 0ca1c891c4e9dde2a071eceac339791462f23efe /cps-rest/src | |
parent | 00e49d680dec96a6aebcbb01b17317b6615232b2 (diff) |
Move CPS REST business logic to CPS-Service Layer
(scope limited to methods using PrefixResolver)
- Introduced CPSFacade (for methods invoking multiple CPS Services related to Prefix insertion)
- Introduced DataMapper to combine PrefixResolver & DataMapUtils
- Moved includeDecendants boolean to Enum conversion to Enum class
- Removed redundant tests from DataRestControllerSpec
- Removed redundant tests from QueryRestControllerSpec
- Cleaned up some legacy testware (adding assert etc)
Issue-ID: CPS-2428
Change-Id: Ib3b4dae941ada441be0dc76aaa5cd14e48685cf7
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Diffstat (limited to 'cps-rest/src')
5 files changed, 153 insertions, 462 deletions
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java index be552ecc6a..b6a2e42a14 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021-2025 Nordix Foundation * Modifications Copyright (C) 2022-2024 TechMahindra Ltd. * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ @@ -30,24 +30,19 @@ import io.micrometer.core.annotation.Timed; import jakarta.validation.ValidationException; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; -import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; -import org.onap.cps.api.model.Anchor; -import org.onap.cps.api.model.DataNode; +import org.onap.cps.api.CpsFacade; import org.onap.cps.api.model.DeltaReport; import org.onap.cps.api.parameters.FetchDescendantsOption; import org.onap.cps.rest.api.CpsDataApi; import org.onap.cps.utils.ContentType; -import org.onap.cps.utils.DataMapUtils; import org.onap.cps.utils.JsonObjectMapper; -import org.onap.cps.utils.PrefixResolver; import org.onap.cps.utils.XmlFileUtils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -64,10 +59,9 @@ public class DataRestController implements CpsDataApi { private static final String ISO_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; private static final DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_FORMAT); + private final CpsFacade cpsFacade; private final CpsDataService cpsDataService; - private final CpsAnchorService cpsAnchorService; private final JsonObjectMapper jsonObjectMapper; - private final PrefixResolver prefixResolver; @Override public ResponseEntity<String> createNode(final String apiVersion, @@ -116,24 +110,20 @@ public class DataRestController implements CpsDataApi { } @Override - @Timed(value = "cps.data.controller.datanode.get.v1", - description = "Time taken to get data node") + @Timed(value = "cps.data.controller.datanode.get.v1", description = "Time taken to get data node") public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String dataspaceName, final String anchorName, final String xpath, final Boolean includeDescendants) { - final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants) - ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS; - final DataNode dataNode = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath, - fetchDescendantsOption).iterator().next(); - final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); - final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath()); - return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK); + final FetchDescendantsOption fetchDescendantsOption = + FetchDescendantsOption.getFetchDescendantsOption(includeDescendants); + final Map<String, Object> dataNodeAsMap = + cpsFacade.getFirstDataNodeByAnchor(dataspaceName, anchorName, xpath, fetchDescendantsOption); + return new ResponseEntity<>(dataNodeAsMap, HttpStatus.OK); } @Override - @Timed(value = "cps.data.controller.datanode.get.v2", - description = "Time taken to get data node") + @Timed(value = "cps.data.controller.datanode.get.v2", description = "Time taken to get data node") public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName, final String xpath, final String fetchDescendantsOptionAsString, @@ -141,16 +131,9 @@ public class DataRestController implements CpsDataApi { final ContentType contentType = ContentType.fromString(contentTypeInHeader); final FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); - final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath, - fetchDescendantsOption); - final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size()); - final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); - for (final DataNode dataNode: dataNodes) { - final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath()); - final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix); - dataMaps.add(dataMap); - } - return buildResponseEntity(dataMaps, contentType); + final List<Map<String, Object>> dataNodesAsMaps = + cpsFacade.getDataNodesByAnchor(dataspaceName, anchorName, xpath, fetchDescendantsOption); + return buildResponseEntity(dataNodesAsMaps, contentType); } @Override diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java index f8833094cf..11713ad5e7 100644 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation + * Copyright (C) 2021-2025 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada. * Modifications Copyright (C) 2022-2024 TechMahindra Ltd. * ================================================================================ @@ -23,23 +23,15 @@ package org.onap.cps.rest.controller; import io.micrometer.core.annotation.Timed; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.onap.cps.api.CpsAnchorService; -import org.onap.cps.api.CpsQueryService; -import org.onap.cps.api.model.Anchor; -import org.onap.cps.api.model.DataNode; +import org.onap.cps.api.CpsFacade; import org.onap.cps.api.parameters.FetchDescendantsOption; import org.onap.cps.api.parameters.PaginationOption; import org.onap.cps.rest.api.CpsQueryApi; import org.onap.cps.utils.ContentType; -import org.onap.cps.utils.DataMapUtils; import org.onap.cps.utils.JsonObjectMapper; -import org.onap.cps.utils.PrefixResolver; import org.onap.cps.utils.XmlFileUtils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -51,27 +43,24 @@ import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor public class QueryRestController implements CpsQueryApi { - private final CpsQueryService cpsQueryService; - private final CpsAnchorService cpsAnchorService; + private final CpsFacade cpsFacade; private final JsonObjectMapper jsonObjectMapper; - private final PrefixResolver prefixResolver; @Override - @Timed(value = "cps.data.controller.datanode.query.v1", - description = "Time taken to query data nodes") + @Timed(value = "cps.data.controller.datanode.query.v1", description = "Time taken to query data nodes") public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String dataspaceName, final String anchorName, final String cpsPath, final Boolean includeDescendants) { - final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants) - ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS; - return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath, - fetchDescendantsOption, ContentType.JSON); + final FetchDescendantsOption fetchDescendantsOption = + FetchDescendantsOption.getFetchDescendantsOption(includeDescendants); + final List<Map<String, Object>> dataNodesAsMaps + = cpsFacade.executeAnchorQuery(dataspaceName, anchorName, cpsPath, fetchDescendantsOption); + return buildResponseEntity(dataNodesAsMaps, ContentType.JSON); } @Override - @Timed(value = "cps.data.controller.datanode.query.v2", - description = "Time taken to query data nodes") + @Timed(value = "cps.data.controller.datanode.query.v2", description = "Time taken to query data nodes") public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPathV2(final String dataspaceName, final String anchorName, final String cpsPath, @@ -80,8 +69,9 @@ public class QueryRestController implements CpsQueryApi { final ContentType contentType = ContentType.fromString(contentTypeInHeader); final FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); - return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath, - fetchDescendantsOption, contentType); + final List<Map<String, Object>> dataNodesAsMaps + = cpsFacade.executeAnchorQuery(dataspaceName, anchorName, cpsPath, fetchDescendantsOption); + return buildResponseEntity(dataNodesAsMaps, contentType); } @Override @@ -96,65 +86,21 @@ public class QueryRestController implements CpsQueryApi { FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); final PaginationOption paginationOption = (pageIndex == null || pageSize == null) ? PaginationOption.NO_PAGINATION : new PaginationOption(pageIndex, pageSize); - final Collection<DataNode> dataNodes = cpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, - cpsPath, fetchDescendantsOption, paginationOption); - final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size()); - String prefix = null; - final Map<String, List<DataNode>> dataNodesPerAnchor = groupDataNodesPerAnchor(dataNodes); - for (final Map.Entry<String, List<DataNode>> dataNodesPerAnchorEntry : dataNodesPerAnchor.entrySet()) { - final String anchorName = dataNodesPerAnchorEntry.getKey(); - if (prefix == null) { - final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); - prefix = prefixResolver.getPrefix(anchor, dataNodesPerAnchorEntry.getValue().get(0).getXpath()); - } - final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifierAndAnchor( - dataNodesPerAnchorEntry.getValue(), anchorName, prefix); - dataNodesAsListOfMaps.add(dataMap); - } - final Integer totalPages = getTotalPages(dataspaceName, cpsPath, paginationOption); - return ResponseEntity.ok().header("total-pages", - totalPages.toString()).body(jsonObjectMapper.asJsonString(dataNodesAsListOfMaps)); - } + final List<Map<String, Object>> dataNodesAsMaps + = cpsFacade.executeDataspaceQuery(dataspaceName, cpsPath, fetchDescendantsOption, paginationOption); - private Integer getTotalPages(final String dataspaceName, final String cpsPath, - final PaginationOption paginationOption) { - if (paginationOption == PaginationOption.NO_PAGINATION) { - return 1; - } - final int totalAnchors = cpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath); - return totalAnchors <= paginationOption.getPageSize() ? 1 - : (int) Math.ceil((double) totalAnchors / paginationOption.getPageSize()); - } - - private static Map<String, List<DataNode>> groupDataNodesPerAnchor(final Collection<DataNode> dataNodes) { - return dataNodes.stream().collect(Collectors.groupingBy(DataNode::getAnchorName)); - } - - private ResponseEntity<Object> executeNodesByDataspaceQueryAndCreateResponse(final String dataspaceName, - final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption, - final ContentType contentType) { - final Collection<DataNode> dataNodes = - cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption); - final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size()); - final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); - String prefix = null; - for (final DataNode dataNode : dataNodes) { - if (prefix == null) { - prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath()); - } - final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix); - dataNodesAsListOfMaps.add(dataMap); - } - return buildResponseEntity(dataNodesAsListOfMaps, contentType); + final int totalPages = cpsFacade.countAnchorsInDataspaceQuery(dataspaceName, cpsPath, paginationOption); + return ResponseEntity.ok().header("total-pages", String.valueOf(totalPages)) + .body(jsonObjectMapper.asJsonString(dataNodesAsMaps)); } - private ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataNodesAsListOfMaps, + private ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataNodesAsMaps, final ContentType contentType) { final String responseData; if (ContentType.XML.equals(contentType)) { - responseData = XmlFileUtils.convertDataMapsToXml(dataNodesAsListOfMaps); + responseData = XmlFileUtils.convertDataMapsToXml(dataNodesAsMaps); } else { - responseData = jsonObjectMapper.asJsonString(dataNodesAsListOfMaps); + responseData = jsonObjectMapper.asJsonString(dataNodesAsMaps); } return new ResponseEntity<>(responseData, HttpStatus.OK); } diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy index f2f962422f..e4cd8c4be6 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation + * Copyright (C) 2021-2025 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada. * Modifications Copyright (C) 2022 Deutsche Telekom AG @@ -25,17 +25,12 @@ package org.onap.cps.rest.controller import com.fasterxml.jackson.databind.ObjectMapper -import groovy.json.JsonSlurper -import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService -import org.onap.cps.api.parameters.FetchDescendantsOption -import org.onap.cps.api.model.DataNode -import org.onap.cps.impl.DataNodeBuilder +import org.onap.cps.api.CpsFacade import org.onap.cps.impl.DeltaReportBuilder import org.onap.cps.utils.ContentType import org.onap.cps.utils.DateTimeUtility import org.onap.cps.utils.JsonObjectMapper -import org.onap.cps.utils.PrefixResolver import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value @@ -44,7 +39,6 @@ import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.mock.web.MockMultipartFile import org.springframework.test.web.servlet.MockMvc -import org.springframework.web.multipart.MultipartFile import spock.lang.Shared import spock.lang.Specification @@ -61,17 +55,14 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder class DataRestControllerSpec extends Specification { @SpringBean - CpsDataService mockCpsDataService = Mock() + CpsFacade mockCpsFacade = Mock() @SpringBean - CpsAnchorService mockCpsAnchorService = Mock() + CpsDataService mockCpsDataService = Mock() @SpringBean JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - @SpringBean - PrefixResolver prefixResolver = Mock() - @Autowired MockMvc mvc @@ -97,20 +88,7 @@ class DataRestControllerSpec extends Specification { def expectedXmlData = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>' @Shared - static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/parent-1') - .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - - @Shared - static DataNode dataNodeWithLeavesNoChildren2 = new DataNodeBuilder().withXpath('/parent-2') - .withLeaves([leaf: 'value']).build() - - @Shared - static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent') - .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build() - - @Shared - static MultipartFile multipartYangFile = new MockMultipartFile("file", 'filename.yang', "text/plain", 'content'.getBytes()) - + def multipartYangFile = new MockMultipartFile("file", 'filename.yang', "text/plain", 'content'.getBytes()) def setup() { dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName" @@ -130,7 +108,7 @@ class DataRestControllerSpec extends Specification { ).andReturn().response then: 'a created response is returned' response.status == HttpStatus.CREATED.value() - then: 'the java API was called with the correct parameters' + then: 'the cps data service was called with the correct parameters' 1 * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData, noTimestamp, expectedContentType) where: 'following xpath parameters are are used' scenario | parentNodeXpath | contentType | expectedContentType | requestBody | expectedData @@ -140,7 +118,7 @@ class DataRestControllerSpec extends Specification { 'XML content: xpath parameter point root' | '/' | MediaType.APPLICATION_XML | ContentType.XML | requestBodyXml | expectedXmlData } - def 'Create a node with observed-timestamp'() { + def 'Create a node with observed-timestamp.'() { given: 'endpoint to create a node' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" when: 'post is invoked with datanode endpoint and json' @@ -154,7 +132,7 @@ class DataRestControllerSpec extends Specification { ).andReturn().response then: 'a created response is returned' response.status == expectedHttpStatus.value() - then: 'the java API was called with the correct parameters' + then: 'the cps data service was called with the correct parameters' expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType) where: @@ -164,7 +142,7 @@ class DataRestControllerSpec extends Specification { 'with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON } - def 'Validate data using create a node API'() { + def 'Validate data using create a node API.'() { given: 'an endpoint to create a node' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" def parentNodeXpath = '/' @@ -181,11 +159,11 @@ class DataRestControllerSpec extends Specification { ).andReturn().response then: 'a 200 OK response is returned' response.status == HttpStatus.OK.value() - then: 'the service was called with correct parameters' + then: 'the cps data service was called with correct parameters' 1 * mockCpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, requestBodyJson, ContentType.JSON) } - def 'Create a child node #scenario'() { + def 'Create a child node #scenario.'() { given: 'endpoint to create a node' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" and: 'parent node xpath' @@ -201,7 +179,7 @@ class DataRestControllerSpec extends Specification { mvc.perform(postRequestBuilder).andReturn().response then: 'a created response is returned' response.status == HttpStatus.CREATED.value() - then: 'the java API was called with the correct parameters' + then: 'the cps data service was called with the correct parameters' 1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, expectedData, DateTimeUtility.toOffsetDateTime(observedTimestamp), expectedContentType) where: @@ -251,10 +229,10 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform(postRequestBuilder).andReturn().response then: 'a created response is returned' response.status == expectedHttpStatus.value() - then: 'the java API was called with the correct parameters' + then: 'the cps data service was called with the correct parameters when needed' expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType) - where: + where: 'the following parameters are used' scenario | observedTimestamp | contentType | requestBody || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType 'Content type JSON with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON 'Content type JSON without observed-timestamp' | null | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON @@ -280,34 +258,14 @@ class DataRestControllerSpec extends Specification { ).andReturn().response then: 'a 200 OK response is returned' response.status == HttpStatus.OK.value() - then: 'the service was called with correct parameters' + then: 'the cps data service was called with correct parameters' 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON) } - def 'Get data node with leaves'() { - given: 'the service returns data node leaves' - def xpath = 'parent-1' - def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node" - mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren] - when: 'get request is performed through REST API' - def response = - mvc.perform(get(endpoint).param('xpath', xpath)) - .andReturn().response - then: 'a success response is returned' - response.status == HttpStatus.OK.value() - then: 'the response contains the the datanode in json format' - response.getContentAsString() == '{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}' - and: 'response contains expected leaf and value' - response.contentAsString.contains('"leaf":"value"') - and: 'response contains expected leaf-list and values' - response.contentAsString.contains('"leafList":["leaveListElement1","leaveListElement2"]') - } - - def 'Get data node with #scenario.'() { + def 'Get data nodes [V1] with #scenario.'() { given: 'the service returns data node with #scenario' - def xpath = 'some xPath' + def xpath = 'my/path' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node" - mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode] when: 'get request is performed through REST API' def response = mvc.perform( @@ -315,121 +273,42 @@ class DataRestControllerSpec extends Specification { .param('xpath', xpath) .param('include-descendants', includeDescendantsOption)) .andReturn().response + then: 'the cps facade is called with the correct parameters' + 1 * mockCpsFacade.getFirstDataNodeByAnchor(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [mocked:'result'] then: 'a success response is returned' response.status == HttpStatus.OK.value() - and: 'the response contains the root node identifier: #expectedRootidentifier' - response.contentAsString.contains(expectedRootidentifier) - and: 'the response contains child is #expectChildInResponse' - response.contentAsString.contains('"child"') == expectChildInResponse - where: - scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier - 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1' - 'no descendant explicitly' | dataNodeWithLeavesNoChildren | 'false' || OMIT_DESCENDANTS | false | 'parent-1' - 'with descendants' | dataNodeWithChild | 'true' || INCLUDE_ALL_DESCENDANTS | true | 'parent' - } - - def 'Get all the data trees as json array with root node xPath using V2'() { - given: 'the service returns all data node leaves' - def xpath = '/' - def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" - mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2] - when: 'V2 of get request is performed through REST API' - def response = - mvc.perform(get(endpoint) - .contentType(MediaType.APPLICATION_JSON) - .param('xpath', xpath)) - .andReturn().response - then: 'a success response is returned' - response.status == HttpStatus.OK.value() - and: 'the response contains the datanode in json array format' - response.getContentAsString() == '[{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}},' + - '{"parent-2":{"leaf":"value"}}]' - and: 'the json array contains expected number of data trees' - def numberOfDataTrees = new JsonSlurper().parseText(response.getContentAsString()).iterator().size() - assert numberOfDataTrees == 2 - } - - def 'Get all the data trees using V2 without Content-Type defaults to json'() { - given: 'the service returns all data node leaves' - def xpath = '/' - def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" - mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2] - when: 'V2 of get request is performed through REST API without specifying content-type header' - def response = - mvc.perform(get(endpoint) - .param('xpath', xpath)) - .andReturn().response - then: 'a success response is returned' - response.status == HttpStatus.OK.value() - and: 'the response contains the datanode in json array format' - response.getContentAsString() == '[{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}},' + - '{"parent-2":{"leaf":"value"}}]' - } - - def 'Get all the data trees as XML with root node xPath using V2'() { - given: 'the service returns all data node leaves' - def xpath = '/' - def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" - mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren] - when: 'V2 of get request is performed through REST API with XML content type' - def response = - mvc.perform(get(endpoint).contentType(MediaType.APPLICATION_XML).param('xpath', xpath)) - .andReturn().response - then: 'a success response is returned' - response.status == HttpStatus.OK.value() - and: 'the response contains the datanode in XML format' - response.getContentAsString() == '<parent-1><leaf>value</leaf><leafList>leaveListElement1</leafList><leafList>leaveListElement2</leafList></parent-1>' + and: 'the response contains the facade result in json format' + response.getContentAsString() == '{"mocked":"result"}' + where: 'the following parameters are used' + scenario | includeDescendantsOption || expectedCpsDataServiceOption + 'no descendants (default) ' | '' || OMIT_DESCENDANTS + 'with descendants' | 'true' || INCLUDE_ALL_DESCENDANTS } - def 'Get data node with #scenario using V2.'() { + def 'Get data node with #scenario using V2. output type #scenario.'() { given: 'the service returns data nodes with #scenario' def xpath = 'some xPath' def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" - mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode] when: 'V2 of get request is performed through REST API' def response = - mvc.perform( - get(endpoint) - .contentType(MediaType.APPLICATION_JSON) - .param('xpath', xpath) - .param('descendants', includeDescendantsOption)) - .andReturn().response - then: 'a success response is returned' - response.status == HttpStatus.OK.value() - and: 'the response contains the root node identifier: #expectedRootidentifier' - response.contentAsString.contains(expectedRootidentifier) - and: 'the response contains child is #expectChildInResponse' - response.contentAsString.contains('"child"') == expectChildInResponse - where: - scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier - 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1' - 'no descendant explicitly' | dataNodeWithLeavesNoChildren | '0' || OMIT_DESCENDANTS | false | 'parent-1' - 'with descendants' | dataNodeWithChild | '-1' || INCLUDE_ALL_DESCENDANTS | true | 'parent' - } - - def 'Get data node using v2 api'() { - given: 'the service returns data node' - def xpath = 'some xPath' - def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" - mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, { descendantsOption -> { - assert descendantsOption.depth == 2}} as FetchDescendantsOption) >> [dataNodeWithChild] - when: 'get request is performed through REST API' - def response = - mvc.perform( - get(endpoint) - .contentType(MediaType.APPLICATION_JSON) + mvc.perform(get(endpoint) + .contentType(contentType) .param('xpath', xpath) - .param('descendants', '2')) + .param('descendants', 'all')) .andReturn().response - then: 'a success response is returned' + then: 'the cps service facade is called with the correct parameters and returns some data' + 1 * mockCpsFacade.getDataNodesByAnchor(dataspaceName, anchorName, xpath, INCLUDE_ALL_DESCENDANTS) >> [[mocked:'result1'], [mocked:'result2']] + and: 'a success response is returned' assert response.status == HttpStatus.OK.value() - and: 'the response contains the root node identifier' - assert response.contentAsString.contains('parent') - and: 'the response contains child is true' - assert response.contentAsString.contains('"child"') + and: 'the response is in the expected format' + assert response.contentAsString == expectedResult + where: 'the following content types are used' + scenario | contentType || expectedResult + 'XML' | MediaType.APPLICATION_XML || '<mocked>result1</mocked><mocked>result2</mocked>' + 'JSON' | MediaType.APPLICATION_JSON || '[{"mocked":"result1"},{"mocked":"result2"}]' } - def 'Get delta between two anchors'() { + def 'Get delta between two anchors.'() { given: 'the service returns a list containing delta reports' def deltaReports = new DeltaReportBuilder().actionReplace().withXpath('some xpath').withSourceData('some key': 'some value').withTargetData('some key': 'some value').build() def xpath = 'some xpath' @@ -468,7 +347,7 @@ class DataRestControllerSpec extends Specification { assert response.contentAsString.contains("[{\"action\":\"create\",\"xpath\":\"some xpath\"}]") } - def 'Get delta between anchor and JSON payload without multipart file'() { + def 'Get delta between anchor and JSON payload without multipart file.'() { given: 'sample delta report, xpath, and json payload' def deltaReports = new DeltaReportBuilder().actionRemove().withXpath('some xpath').build() def xpath = 'some xpath' @@ -499,7 +378,7 @@ class DataRestControllerSpec extends Specification { .content(requestBody) .param('xpath', inputXpath) ).andReturn().response - then: 'the service method is invoked with expected parameters' + then: 'the cps data service method is invoked with expected parameters' 1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, expectedData, null, expectedContentType) and: 'response status indicates success' response.status == HttpStatus.OK.value() @@ -513,7 +392,7 @@ class DataRestControllerSpec extends Specification { 'XML content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_XML || '/some/xpath' | requestBodyXml | expectedXmlData | ContentType.XML } - def 'Update data node leaves with observedTimestamp'() { + def 'Update data node leaves with observedTimestamp.'() { given: 'endpoint to update a node leaves ' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" when: 'patch request is performed' @@ -525,7 +404,7 @@ class DataRestControllerSpec extends Specification { .param('xpath', '/') .param('observed-timestamp', observedTimestamp) ).andReturn().response - then: 'the service method is invoked with expected parameters' + then: 'the cps data service method is invoked with expected parameters' expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', expectedJsonData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON) and: 'response status indicates success' @@ -536,7 +415,7 @@ class DataRestControllerSpec extends Specification { 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST } - def 'Validate data using Update a node API'() { + def 'Validate data using Update a node API.'() { given: 'endpoint to update a node leaves' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" and: 'dryRunEnabled flag is set to true' @@ -552,7 +431,7 @@ class DataRestControllerSpec extends Specification { ).andReturn().response then: 'a 200 OK response is returned' response.status == HttpStatus.OK.value() - then: 'the service was called with correct parameters' + then: 'the cps data service was called with correct parameters' 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON) } @@ -567,7 +446,7 @@ class DataRestControllerSpec extends Specification { .content(requestBody) .param('xpath', inputXpath)) .andReturn().response - then: 'the service method is invoked with expected parameters' + then: 'the cps data service method is invoked with expected parameters' 1 * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, xpathServiceParameter, expectedData, noTimestamp, expectedContentType) and: 'response status indicates success' response.status == HttpStatus.OK.value() @@ -581,7 +460,7 @@ class DataRestControllerSpec extends Specification { 'XML content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_XML || '/some/xpath' | requestBodyXml | expectedXmlData | ContentType.XML } - def 'Validate data using Replace data node API'() { + def 'Validate data using Replace data node API.'() { given: 'endpoint to replace node' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" and: 'dryRunEnabled flag is set to true' @@ -597,7 +476,7 @@ class DataRestControllerSpec extends Specification { ).andReturn().response then: 'a 200 OK response is returned' response.status == HttpStatus.OK.value() - then: 'the service was called with correct parameters' + then: 'the cps data service was called with correct parameters' 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON) } @@ -613,7 +492,7 @@ class DataRestControllerSpec extends Specification { .param('xpath', '') .param('observed-timestamp', observedTimestamp)) .andReturn().response - then: 'the service method is invoked with expected parameters' + then: 'the cps data service method is invoked with expected parameters' expectedApiCount * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, '/', expectedJsonData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON) and: 'response status indicates success' @@ -635,7 +514,7 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform(putRequestBuilder).andReturn().response then: 'a success response is returned' response.status == expectedHttpStatus.value() - and: 'the java API was called with the correct parameters' + and: 'the cps data service was called with the correct parameters' expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedJsonData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON) where: @@ -656,7 +535,7 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform(putRequestBuilder).andReturn().response then: 'a success response is returned' response.status == expectedHttpStatus.value() - and: 'the java API was called with the correct parameters' + and: 'the cps data service was called with the correct parameters' expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedXmlData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.XML) where: @@ -666,7 +545,7 @@ class DataRestControllerSpec extends Specification { 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST } - def 'Validate data using Replace list content API'() { + def 'Validate data using Replace list content API.'() { given: 'endpoint to replace list-nodes' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes" and: 'dryRunEnabled flag is set to true' @@ -682,7 +561,7 @@ class DataRestControllerSpec extends Specification { ).andReturn().response then: 'a 200 OK response is returned' response.status == HttpStatus.OK.value() - then: 'the service was called with correct parameters' + then: 'the cps data service was called with correct parameters' 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON) } @@ -695,7 +574,7 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform(deleteRequestBuilder).andReturn().response then: 'a success response is returned' response.status == expectedHttpStatus.value() - and: 'the java API was called with the correct parameters' + and: 'the cps data service was called with the correct parameters' expectedApiCount * mockCpsDataService.deleteListOrListElement(dataspaceName, anchorName, 'list element xpath', { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) where: @@ -717,7 +596,7 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform(deleteDataNodeRequest).andReturn().response then: 'a successful response is returned' response.status == expectedHttpStatus.value() - and: 'the api is called with the correct parameters' + and: 'the cps data service is called with the correct parameters' expectedApiCount * mockCpsDataService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) where: diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy index 2b5c471287..5f6de2ec4c 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation + * Copyright (C) 2021-2025 Nordix Foundation * Modifications Copyright (C) 2021-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022-2024 TechMahindra Ltd. @@ -24,12 +24,9 @@ package org.onap.cps.rest.controller import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.api.CpsAnchorService -import org.onap.cps.api.CpsQueryService +import org.onap.cps.api.CpsFacade import org.onap.cps.api.parameters.PaginationOption -import org.onap.cps.impl.DataNodeBuilder import org.onap.cps.utils.JsonObjectMapper -import org.onap.cps.utils.PrefixResolver import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value @@ -39,216 +36,98 @@ import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc import spock.lang.Specification -import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS +import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get @WebMvcTest(QueryRestController) class QueryRestControllerSpec extends Specification { @SpringBean - CpsQueryService mockCpsQueryService = Mock() - - @SpringBean - CpsAnchorService mockCpsAnchorService = Mock() + CpsFacade mockCpsFacade = Mock() @SpringBean JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - @SpringBean - PrefixResolver prefixResolver = Mock() - @Autowired MockMvc mvc @Value('${rest.api.cps-base-path}') def basePath - def dataspaceName = 'my_dataspace' - def anchorName = 'my_anchor' - def cpsPath = 'some cps-path' - def dataNodeEndpointV2 - - def setup() { - dataNodeEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query" - } + def dataNodeAsMap = ['prefixedPath':[path:[leaf:'value']]] - def 'Query data node by cps path for the given dataspace and anchor with #scenario.'() { - given: 'service method returns a list containing a data node' - def dataNode1 = new DataNodeBuilder().withXpath('/xpath') - .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, expectedCpsDataServiceOption) >> [dataNode1, dataNode1] - and: 'the query endpoint' - def dataNodeEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query" + def 'Query data node (v1) by cps path for the given dataspace and anchor with #scenario.'() { + given: 'the query endpoint' + def dataNodeEndpoint = "$basePath/v1/dataspaces/my_dataspace/anchors/my_anchor/nodes/query" when: 'query data nodes API is invoked' - def response = - mvc.perform( - get(dataNodeEndpoint) - .param('cps-path', cpsPath) - .param('include-descendants', includeDescendantsOption)) - .andReturn().response + def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', 'my/path').param('include-descendants', includeDescendantsOption)) + .andReturn().response + then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map' + 1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', 'my/path', expectedCpsDataServiceOption) >> [dataNodeAsMap] then: 'the response contains the the datanode in json format' - response.status == HttpStatus.OK.value() - response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') + assert response.status == HttpStatus.OK.value() + assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]' where: 'the following options for include descendants are provided in the request' scenario | includeDescendantsOption || expectedCpsDataServiceOption 'no descendants by default' | '' || OMIT_DESCENDANTS - 'no descendant explicitly' | 'false' || OMIT_DESCENDANTS 'descendants' | 'true' || INCLUDE_ALL_DESCENDANTS } - def 'Query data node v2 API by cps path for the given dataspace and anchor with #scenario and media type JSON'() { - given: 'service method returns a list containing a data node' - def dataNode = new DataNodeBuilder().withXpath('/xpath') - .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption -> - assert descendantsOption.depth == expectedDepth - }) >> [dataNode, dataNode] + def 'Query data node (v2) by cps path for given dataspace and anchor with #scenario'() { + given: 'the query endpoint' + def dataNodeEndpointV2 = "$basePath/v2/dataspaces/my_dataspace/anchors/my_anchor/nodes/query" when: 'query data nodes API is invoked' - def response = - mvc.perform( - get(dataNodeEndpointV2) - .contentType(MediaType.APPLICATION_JSON) - .param('cps-path', cpsPath) - .param('descendants', includeDescendantsOptionString)) + def response = mvc.perform(get(dataNodeEndpointV2).contentType(contentType).param('cps-path', 'my/path') .param('descendants', includeDescendantsOptionString)) .andReturn().response - then: 'the response contains the datanode in the expected JSON format' + then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map' + 1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', 'my/path', + { descendantsOption -> assert descendantsOption.depth == expectedDepth }) >> [dataNodeAsMap] + and: 'the response contains the datanode in the expected format' assert response.status == HttpStatus.OK.value() - assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') + assert response.getContentAsString() == expectedOutput where: 'the following options for include descendants are provided in the request' - scenario | includeDescendantsOptionString || expectedDepth - 'direct children' | 'direct' || 1 - 'descendants' | '2' || 2 + scenario | includeDescendantsOptionString | contentType || expectedDepth || expectedOutput + 'direct children JSON' | 'direct' | MediaType.APPLICATION_JSON || 1 || '[{"prefixedPath":{"path":{"leaf":"value"}}}]' + 'descendants JSON' | '2' | MediaType.APPLICATION_JSON || 2 || '[{"prefixedPath":{"path":{"leaf":"value"}}}]' + 'descendants XML' | '2' | MediaType.APPLICATION_XML || 2 || '<prefixedPath><path><leaf>value</leaf></path></prefixedPath>' } - def 'Query data node v2 API by cps path for the given dataspace and anchor with #scenario and media type XML'() { - given: 'service method returns a list containing a data node' - def dataNode = new DataNodeBuilder().withXpath('/xpath') - .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption -> - assert descendantsOption.depth == expectedDepth - }) >> [dataNode, dataNode] + def 'Query data node by cps path for given dataspace across all anchors'() { + given: 'the query endpoint' + def dataNodeEndpoint = "$basePath/v2/dataspaces/my_dataspace/nodes/query" + and: 'the cps service facade will say there are 123 pages ' + mockCpsFacade.countAnchorsInDataspaceQuery('my_dataspace', 'my/path', new PaginationOption(2,5) ) >> 123 when: 'query data nodes API is invoked' - def response = - mvc.perform( - get(dataNodeEndpointV2) - .contentType(MediaType.APPLICATION_XML) - .param('cps-path', cpsPath) - .param('descendants', includeDescendantsOptionString)) - .andReturn().response - then: 'the response contains the datanode in the expected XML format' - assert response.status == HttpStatus.OK.value() - assert response.getContentAsString().contains('<xpath><leaf>value</leaf><leafList>leaveListElement1</leafList><leafList>leaveListElement2</leafList></xpath>') - where: 'the following options for include descendants are provided in the request' - scenario | includeDescendantsOptionString || expectedDepth - 'direct children' | 'direct' || 1 - 'descendants' | '2' || 2 - } - - def 'Query data node by cps path for the given dataspace across all anchors with #scenario.'() { - given: 'service method returns a list containing a data node from different anchors' - def dataNode1 = new DataNodeBuilder().withXpath('/xpath') - .withAnchor('my_anchor') - .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - def dataNode2 = new DataNodeBuilder().withXpath('/xpath') - .withAnchor('my_anchor_2') - .withLeaves([leaf: 'value', leafList: ['leaveListElement3', 'leaveListElement4']]).build() - and: 'second data node for the same anchor' - def dataNode3 = new DataNodeBuilder().withXpath('/xpath') - .withAnchor('my_anchor_2') - .withLeaves([leaf: 'value', leafList: ['leaveListElement5', 'leaveListElement6']]).build() - and: 'the query endpoint' - def dataspaceName = 'my_dataspace' - def cpsPath = 'some/cps/path' - def dataNodeEndpoint = "$basePath/v2/dataspaces/$dataspaceName/nodes/query" - mockCpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, - expectedCpsDataServiceOption, PaginationOption.NO_PAGINATION) >> [dataNode1, dataNode2, dataNode3] - mockCpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath) >> 2 - when: 'query data nodes API is invoked' - def response = - mvc.perform( - get(dataNodeEndpoint) - .param('cps-path', cpsPath) - .param('descendants', includeDescendantsOptionString)) + def response = mvc.perform( + get(dataNodeEndpoint).param('cps-path', 'my/path').param('pageIndex', String.valueOf(2)).param('pageSize', String.valueOf(5))) .andReturn().response - then: 'the response contains the the datanode in json format' - response.status == HttpStatus.OK.value() - response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') - response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}') - response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement5","leaveListElement6"]}}') - where: 'the following options for include descendants are provided in the request' - scenario | includeDescendantsOptionString || expectedCpsDataServiceOption - 'no descendants by default' | '' || OMIT_DESCENDANTS - 'no descendant explicitly' | 'none' || OMIT_DESCENDANTS - 'descendants' | 'all' || INCLUDE_ALL_DESCENDANTS - 'direct children' | 'direct' || DIRECT_CHILDREN_ONLY - } - - def 'Query data node by cps path for the given dataspace across all anchors with pagination #scenario.'() { - given: 'service method returns a list containing a data node from different anchors' - def dataNode1 = new DataNodeBuilder().withXpath('/xpath') - .withAnchor('my_anchor') - .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - def dataNode2 = new DataNodeBuilder().withXpath('/xpath') - .withAnchor('my_anchor_2') - .withLeaves([leaf: 'value', leafList: ['leaveListElement3', 'leaveListElement4']]).build() - and: 'the query endpoint' - def dataspaceName = 'my_dataspace' - def cpsPath = 'some/cps/path' - def dataNodeEndpoint = "$basePath/v2/dataspaces/$dataspaceName/nodes/query" - mockCpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, - INCLUDE_ALL_DESCENDANTS, new PaginationOption(pageIndex,pageSize)) >> [dataNode1, dataNode2] - mockCpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath) >> totalAnchors - when: 'query data nodes API is invoked' - def response = - mvc.perform( - get(dataNodeEndpoint) - .param('cps-path', cpsPath) - .param('descendants', "all") - .param('pageIndex', String.valueOf(pageIndex)) - .param('pageSize', String.valueOf(pageSize))) - .andReturn().response - then: 'the response contains the the datanode in json format' + then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map' + 1 * mockCpsFacade.executeDataspaceQuery('my_dataspace', 'my/path', OMIT_DESCENDANTS, new PaginationOption(2,5)) >> [dataNodeAsMap] + then: 'the response is OK and contains the the datanode in json format' assert response.status == HttpStatus.OK.value() - assert Integer.valueOf(response.getHeaderValue("total-pages")) == expectedTotalPageSize - assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') - assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}') - where: 'the following options for include descendants are provided in the request' - scenario | pageIndex | pageSize | totalAnchors || expectedTotalPageSize - '1st page with all anchors' | 1 | 3 | 3 || 1 - '1st page with less anchors' | 1 | 2 | 3 || 2 + assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]' + and: 'the header indicates the correct number of pages' + assert response.getHeaderValue('total-pages') == '123' } - def 'Query data node across all anchors with pagination option with #scenario.'() { - given: 'service method returns a list containing a data node from different anchors' - def dataNode1 = new DataNodeBuilder().withXpath('/xpath') - .withAnchor('my_anchor') - .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() - def dataNode2 = new DataNodeBuilder().withXpath('/xpath') - .withAnchor('my_anchor_2') - .withLeaves([leaf: 'value', leafList: ['leaveListElement3', 'leaveListElement4']]).build() - and: 'the query endpoint' - def dataspaceName = 'my_dataspace' - def cpsPath = 'some/cps/path' - def dataNodeEndpoint = "$basePath/v2/dataspaces/$dataspaceName/nodes/query" - mockCpsQueryService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, - INCLUDE_ALL_DESCENDANTS, PaginationOption.NO_PAGINATION) >> [dataNode1, dataNode2] - mockCpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath) >> 2 + def 'Query data node across all anchors with pagination option with #scenario i.e. no pagination.'() { + given: 'the query endpoint' + def dataNodeEndpoint = "$basePath/v2/dataspaces/my_dataspace/nodes/query" + and: 'the cps service facade will say there is 1 page ' + mockCpsFacade.countAnchorsInDataspaceQuery('my_dataspace', 'my/path', NO_PAGINATION ) >> 1 when: 'query data nodes API is invoked' - def response = - mvc.perform( - get(dataNodeEndpoint) - .param('cps-path', cpsPath) - .param('descendants', "all") - .param(parameterName, "1")) - .andReturn().response - then: 'the response contains the the datanode in json format' + def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', 'my/path').param(parameterName, '1')) + .andReturn().response + then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map' + 1 * mockCpsFacade.executeDataspaceQuery('my_dataspace', 'my/path', OMIT_DESCENDANTS, PaginationOption.NO_PAGINATION) >> [dataNodeAsMap] + then: 'the response is OK and contains the datanode in json format' assert response.status == HttpStatus.OK.value() - assert Integer.valueOf(response.getHeaderValue("total-pages")) == 1 - assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}') - assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement3","leaveListElement4"]}}') - where: + assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]' + and: 'the header indicates the correct number of pages' + assert response.getHeaderValue('total-pages') == '1' + where: 'only the following rest parameter is used' scenario | parameterName 'only page size' | 'pageSize' 'only page index' | 'pageIndex' diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy index 4e1d27cda2..0cbdffbdc4 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2020 Pantheon.tech - * Modifications Copyright (C) 2021-2023 Nordix Foundation + * Modifications Copyright (C) 2021-2025 Nordix Foundation * Modifications Copyright (C) 2021 Bell Canada. * Modifications Copyright (C) 2022-2025 TechMahindra Ltd. * Modifications Copyright (C) 2022 Deutsche Telekom AG @@ -26,23 +26,24 @@ package org.onap.cps.rest.exceptions import com.fasterxml.jackson.databind.ObjectMapper import groovy.json.JsonSlurper -import org.onap.cps.api.CpsDataspaceService import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsDataspaceService +import org.onap.cps.api.CpsFacade import org.onap.cps.api.CpsModuleService import org.onap.cps.api.CpsNotificationService import org.onap.cps.api.CpsQueryService -import org.onap.cps.rest.controller.CpsRestInputMapper import org.onap.cps.api.exceptions.AlreadyDefinedException import org.onap.cps.api.exceptions.CpsException import org.onap.cps.api.exceptions.CpsPathException import org.onap.cps.api.exceptions.DataInUseException import org.onap.cps.api.exceptions.DataNodeNotFoundException import org.onap.cps.api.exceptions.DataValidationException +import org.onap.cps.api.exceptions.DataspaceInUseException import org.onap.cps.api.exceptions.ModelValidationException import org.onap.cps.api.exceptions.NotFoundInDataspaceException import org.onap.cps.api.exceptions.SchemaSetInUseException -import org.onap.cps.api.exceptions.DataspaceInUseException +import org.onap.cps.rest.controller.CpsRestInputMapper import org.onap.cps.utils.JsonObjectMapper import org.onap.cps.utils.PrefixResolver import org.spockframework.spring.SpringBean @@ -65,6 +66,9 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder class CpsRestExceptionHandlerSpec extends Specification { @SpringBean + CpsFacade mockCpsFacade = Stub() + + @SpringBean CpsDataspaceService mockCpsAdminService = Stub() @SpringBean @@ -86,10 +90,10 @@ class CpsRestExceptionHandlerSpec extends Specification { CpsRestInputMapper cpsRestInputMapper = Stub() @SpringBean - PrefixResolver prefixResolver = Mock() + PrefixResolver prefixResolver = Stub() @SpringBean - CpsNotificationService mockCpsNotificationService = Mock() + CpsNotificationService mockCpsNotificationService = Stub() @Autowired MockMvc mvc |