diff options
12 files changed, 76 insertions, 40 deletions
diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml index e04ff02775..18ed43c993 100755 --- a/cps-dependencies/pom.xml +++ b/cps-dependencies/pom.xml @@ -39,7 +39,7 @@ <releaseNexusPath>/content/repositories/releases/</releaseNexusPath> <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath> <sonar.skip>true</sonar.skip> - <testcontainers.version>1.15.1</testcontainers.version> + <testcontainers.version>1.17.3</testcontainers.version> <mapstruct.version>1.4.2.Final</mapstruct.version> </properties> diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/MessagingSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/MessagingSpec.groovy index 097834afc5..712c2b8f14 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/MessagingSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/MessagingSpec.groovy @@ -41,7 +41,7 @@ class MessagingSpec extends Specification { kafkaTestContainer.start() } - static kafkaTestContainer = new KafkaContainer(DockerImageName.parse('confluentinc/cp-kafka:6.2.1')) + static kafkaTestContainer = new KafkaContainer(DockerImageName.parse('registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1').asCompatibleSubstituteFor('confluentinc/cp-kafka')) def producerConfigProperties() { return [('bootstrap.servers'): kafkaTestContainer.getBootstrapServers().split(',')[0], diff --git a/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java b/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java index 93233d9c47..0c6edd5b4e 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2020 Pantheon.tech * Modifications Copyright (C) 2021-2022 Nordix Foundation + * Modifications Copyright (C) 2022 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +44,7 @@ import org.onap.cps.spi.exceptions.NotFoundInDataspaceException; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -64,7 +66,7 @@ public class CpsRestExceptionHandler { } @ExceptionHandler({ModelValidationException.class, DataValidationException.class, CpsAdminException.class, - CpsPathException.class, ValidationException.class}) + CpsPathException.class, ValidationException.class, HttpMessageNotReadableException.class}) public static ResponseEntity<Object> handleBadRequestExceptions(final Exception exception) { return buildErrorResponse(HttpStatus.BAD_REQUEST, exception); } 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 e9d0e3e12a..ece3507f2d 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 @@ -3,6 +3,7 @@ * Copyright (C) 2020 Pantheon.tech * Modifications Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Bell Canada. + * Modifications Copyright (C) 2022 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -180,6 +181,18 @@ class CpsRestExceptionHandlerSpec extends Specification { exceptionThrown << [new DataNodeNotFoundException('', ''), new NotFoundInDataspaceException('', '')] } + def 'Post request with invalid JSON payload returns HTTP Status Bad Request.'() { + when: 'data post request is performed' + def response = mvc.perform( + post("$basePath/v1/dataspaces/dataspace-name/anchors/anchor-name/nodes") + .contentType(MediaType.APPLICATION_JSON) + .param('xpath', 'parent node xpath') + .content('{') + ).andReturn().response + then: 'response code indicates bad input parameters' + response.status == BAD_REQUEST.value() + } + /* * NB. The test uses 'get anchors' endpoint and associated service method invocation * to test the exception handling. The endpoint chosen is not a subject of test. diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java index 50187a487b..27891c525e 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java @@ -57,6 +57,7 @@ public class FragmentEntityArranger { fragmentEntity.setAttributes(fragmentExtract.getAttributes()); fragmentEntity.setParentId(fragmentExtract.getParentId()); fragmentEntity.setChildFragments(new HashSet<>()); + fragmentEntity.setDataspace(anchorEntity.getDataspace()); return fragmentEntity; } @@ -70,10 +71,7 @@ public class FragmentEntityArranger { parentFragmentEntity.getChildFragments().add(fragmentEntity); } } - if (fragmentEntitiesWithoutParentInResultSet.iterator().hasNext()) { - return fragmentEntitiesWithoutParentInResultSet.iterator().next(); - } - return null; + return fragmentEntitiesWithoutParentInResultSet.stream().findFirst().orElse(null); } } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index dc848e657c..ac1be1cd30 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -230,7 +230,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); if (isRootXpath(xpath)) { - return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity); + final List<FragmentExtract> fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity, + anchorEntity); + final FragmentEntity fragmentEntity = FragmentEntityArranger.toFragmentEntityTree(anchorEntity, + fragmentExtracts); + return fragmentEntity; } else { final String normalizedXpath = getNormalizedXpath(xpath); final FragmentEntity fragmentEntity; @@ -500,7 +504,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final boolean onlySupportListNodeDeletion) { final String parentNodeXpath; FragmentEntity parentFragmentEntity = null; - boolean targetDeleted = false; + boolean targetDeleted; if (isRootXpath(targetXpath)) { deleteDataNodes(dataspaceName, anchorName); targetDeleted = true; diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java index 112ebfda70..2c25a61a7e 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java @@ -1,6 +1,6 @@ /*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2021 Nordix Foundation.
+ * Copyright (C) 2021-2022 Nordix Foundation.
* Modifications Copyright (C) 2020-2021 Bell Canada.
* Modifications Copyright (C) 2020-2021 Pantheon.tech.
* ================================================================================
@@ -58,10 +58,26 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, List<FragmentEntity> findRootsByDataspaceAndAnchor(@Param("dataspace") int dataspaceId,
@Param("anchor") int anchorId);
- default FragmentEntity findFirstRootByDataspaceAndAnchor(@NonNull DataspaceEntity dataspaceEntity,
- @NonNull AnchorEntity anchorEntity) {
- return findRootsByDataspaceAndAnchor(dataspaceEntity.getId(), anchorEntity.getId()).stream().findFirst()
- .orElseThrow(() -> new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName()));
+ @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"
+ + " CAST(attributes AS TEXT) AS attributes"
+ + " FROM FRAGMENT WHERE anchor_id = :anchorId",
+ nativeQuery = true)
+ List<FragmentExtract> findRootsByAnchorId(@Param("anchorId") int anchorId);
+
+ /**
+ * find top level fragment by anchor.
+ *
+ * @param dataspaceEntity dataspace entity
+ * @param anchorEntity anchor entity
+ * @return FragmentEntity fragment entity
+ */
+ default List<FragmentExtract> getTopLevelFragments(DataspaceEntity dataspaceEntity,
+ AnchorEntity anchorEntity) {
+ final List<FragmentExtract> fragmentExtracts = findRootsByAnchorId(anchorEntity.getId());
+ if (fragmentExtracts.isEmpty()) {
+ throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName());
+ }
+ return fragmentExtracts;
}
List<FragmentEntity> findAllByAnchorAndXpathIn(@NonNull AnchorEntity anchorEntity,
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy index a5e17cfefd..fbf414d2ad 100755 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy @@ -56,7 +56,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { static final int ANCHOR_3003_ID = 3003L static final long ID_DATA_NODE_WITH_DESCENDANTS = 4001 static final String XPATH_DATA_NODE_WITH_DESCENDANTS = '/parent-1' - static final String XPATH_DATA_NODE_WITH_LEAVES = '/parent-100' + static final String XPATH_DATA_NODE_WITH_LEAVES = '/parent-207' static final long DATA_NODE_202_FRAGMENT_ID = 4202L static final long CHILD_OF_DATA_NODE_202_FRAGMENT_ID = 4203L static final long LIST_DATA_NODE_PARENT201_FRAGMENT_ID = 4206L @@ -69,10 +69,10 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { static DataNode existingChildDataNode def expectedLeavesByXpathMap = [ - '/parent-100' : ['parent-leaf': 'parent-leaf value'], - '/parent-100/child-001' : ['first-child-leaf': 'first-child-leaf value'], - '/parent-100/child-002' : ['second-child-leaf': 'second-child-leaf value'], - '/parent-100/child-002/grand-child': ['grand-child-leaf': 'grand-child-leaf value'] + '/parent-207' : ['parent-leaf': 'parent-leaf value'], + '/parent-207/child-001' : ['first-child-leaf': 'first-child-leaf value'], + '/parent-207/child-002' : ['second-child-leaf': 'second-child-leaf value'], + '/parent-207/child-002/grand-child': ['grand-child-leaf': 'grand-child-leaf value'] ] static { @@ -196,24 +196,24 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { @Sql([CLEAR_DATA, SET_DATA]) def 'Add multiple list with a mix of existing and new elements'() { given: 'two new child list elements for an existing parent' - def existingDataNode = dataNodeBuilder.withXpath('/parent-100/child-001').withLeaves(['id': '001']).build() - def newDataNode1 = dataNodeBuilder.withXpath('/parent-100/child-new1').withLeaves(['id': 'new1']).build() + def existingDataNode = dataNodeBuilder.withXpath('/parent-207/child-001').withLeaves(['id': '001']).build() + def newDataNode1 = dataNodeBuilder.withXpath('/parent-207/child-new1').withLeaves(['id': 'new1']).build() def newDataNode2 = dataNodeBuilder.withXpath('/parent-200/child-new2').withLeaves(['id': 'new2']).build() def dataNodeList1 = [existingDataNode, newDataNode1] def dataNodeList2 = [newDataNode2] when: 'duplicate data node is requested to be added' - objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/', [dataNodeList1,dataNodeList2]) + objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT, '/', [dataNodeList1, dataNodeList2]) then: 'already defined batch exception is thrown' def thrown = thrown(AlreadyDefinedExceptionBatch) and: 'it only contains the xpath(s) of the duplicated elements' assert thrown.alreadyDefinedXpaths.size() == 1 - assert thrown.alreadyDefinedXpaths.contains('/parent-100/child-001') + assert thrown.alreadyDefinedXpaths.contains('/parent-207/child-001') and: 'it does NOT contains the xpaths of the new element that were not combined with existing elements' - assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1') - assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1') + assert !thrown.alreadyDefinedXpaths.contains('/parent-207/child-new1') + assert !thrown.alreadyDefinedXpaths.contains('/parent-207/child-new1') and: 'the new entity is inserted correctly' def dataspaceEntity = dataspaceRepository.getByName(DATASPACE_NAME) - def anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, ANCHOR_NAME3) + def anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT) fragmentRepository.findByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, '/parent-200/child-new2').isPresent() } @@ -234,7 +234,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { @Sql([CLEAR_DATA, SET_DATA]) def 'Get data node by xpath without descendants.'() { when: 'data node is requested' - def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, + def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT, inputXPath, OMIT_DESCENDANTS) then: 'data node is returned with no descendants' assert result.xpath == XPATH_DATA_NODE_WITH_LEAVES @@ -243,7 +243,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { assertLeavesMaps(result.leaves, expectedLeavesByXpathMap[XPATH_DATA_NODE_WITH_LEAVES]) where: 'the following data is used' scenario | inputXPath - 'some xpath' | '/parent-100' + 'some xpath' | '/parent-207' 'root xpath' | '/' 'empty xpath' | '' } @@ -260,20 +260,20 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { @Sql([CLEAR_DATA, SET_DATA]) def 'Get data node by xpath with all descendants.'() { when: 'data node is requested with all descendants' - def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, + def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT, inputXPath, INCLUDE_ALL_DESCENDANTS) def mappedResult = treeToFlatMapByXpath(new HashMap<>(), result) then: 'data node is returned with all the descendants populated' assert mappedResult.size() == 4 assert result.childDataNodes.size() == 2 - assert mappedResult.get('/parent-100/child-001').childDataNodes.size() == 0 - assert mappedResult.get('/parent-100/child-002').childDataNodes.size() == 1 + assert mappedResult.get('/parent-207/child-001').childDataNodes.size() == 0 + assert mappedResult.get('/parent-207/child-002').childDataNodes.size() == 1 and: 'extracted leaves maps are matching expected' mappedResult.forEach( (xPath, dataNode) -> assertLeavesMaps(dataNode.leaves, expectedLeavesByXpathMap[xPath])) where: 'the following data is used' scenario | inputXPath - 'some xpath' | '/parent-100' + 'some xpath' | '/parent-207' 'root xpath' | '/' 'empty xpath' | '' } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy index d6f10d809d..1ecad4e68c 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy @@ -68,4 +68,5 @@ class CpsPersistenceSpecBase extends Specification { static final String ANCHOR_NAME3 = 'ANCHOR-003' static final String ANCHOR_FOR_DATA_NODES_WITH_LEAVES = 'ANCHOR-003' static final String ANCHOR_FOR_SHOP_EXAMPLE = 'ANCHOR-004' + static final String ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT = 'ANCHOR-005' } diff --git a/cps-ri/src/test/java/org/onap/cps/DatabaseTestContainer.java b/cps-ri/src/test/java/org/onap/cps/DatabaseTestContainer.java index 2d2df2e629..61a5c042a6 100755 --- a/cps-ri/src/test/java/org/onap/cps/DatabaseTestContainer.java +++ b/cps-ri/src/test/java/org/onap/cps/DatabaseTestContainer.java @@ -21,6 +21,7 @@ package org.onap.cps; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; /** * The Postgresql database test container wrapper. @@ -30,11 +31,11 @@ import org.testcontainers.containers.PostgreSQLContainer; * psql -d test -U test */ public class DatabaseTestContainer extends PostgreSQLContainer<DatabaseTestContainer> { - private static final String IMAGE_VERSION = "postgres:14.1"; + private static final String IMAGE_VERSION = "registry.nordix.org/onaptest/postgres:14.1"; private static DatabaseTestContainer databaseTestContainer; private DatabaseTestContainer() { - super(IMAGE_VERSION); + super(DockerImageName.parse(IMAGE_VERSION).asCompatibleSubstituteFor("postgres")); } /** diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql index bc6e4e7c65..ad463cffd5 100755 --- a/cps-ri/src/test/resources/data/fragment.sql +++ b/cps-ri/src/test/resources/data/fragment.sql @@ -51,7 +51,8 @@ INSERT INTO SCHEMA_SET_YANG_RESOURCES (SCHEMA_SET_ID, YANG_RESOURCE_ID) VALUES INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES (3001, 'ANCHOR-001', 1001, 2001), (3003, 'ANCHOR-003', 1001, 2001), - (3004, 'ncmp-dmi-registry', 1002, 2001); + (3004, 'ncmp-dmi-registry', 1002, 2001), + (3005, 'ANCHOR-005', 1001, 2001); INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES (4001, 1001, 3001, null, '/parent-1'), @@ -62,10 +63,10 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES (4006, 1001, 3001, 4004, '/parent-1/child-1/grandchild-1'); INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES - (4101, 1001, 3003, null, '/parent-100', '{"parent-leaf": "parent-leaf value"}'), - (4102, 1001, 3003, 4101, '/parent-100/child-001', '{"first-child-leaf": "first-child-leaf value"}'), - (4103, 1001, 3003, 4101, '/parent-100/child-002', '{"second-child-leaf": "second-child-leaf value"}'), - (4104, 1001, 3003, 4103, '/parent-100/child-002/grand-child', '{"grand-child-leaf": "grand-child-leaf value"}'); + (5009, 1001, 3005, null, '/parent-207', '{"parent-leaf": "parent-leaf value"}'), + (5010, 1001, 3005, 5009, '/parent-207/child-001', '{"first-child-leaf": "first-child-leaf value"}'), + (5011, 1001, 3005, 5009, '/parent-207/child-002', '{"second-child-leaf": "second-child-leaf value"}'), + (5012, 1001, 3005, 5011, '/parent-207/child-002/grand-child', '{"grand-child-leaf": "grand-child-leaf value"}'); INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES (4201, 1001, 3003, null, '/parent-200', '{"leaf-value": "original"}'), diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy index 05b9624a47..b07b31a35b 100644 --- a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy @@ -33,7 +33,7 @@ class KafkaTestContainerConfig { // Not the best performance but it is good enough for test case private static synchronized KafkaContainer getKafkaContainer() { if (kafkaContainer == null) { - kafkaContainer = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:6.2.1")) + kafkaContainer = new KafkaContainer(DockerImageName.parse("registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1").asCompatibleSubstituteFor("confluentinc/cp-kafka")) .withEnv("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "false") kafkaContainer.start() Runtime.getRuntime().addShutdownHook(new Thread(kafkaContainer::stop)) |