package org.onap.cps.ncmp.rest.controller
+import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores
+import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+
import com.fasterxml.jackson.databind.ObjectMapper
import org.mapstruct.factory.Mappers
import org.onap.cps.TestUtils
import org.onap.cps.ncmp.api.NetworkCmProxyDataService
import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
-import org.onap.cps.ncmp.api.inventory.CmHandleState
-import org.onap.cps.ncmp.api.inventory.CompositeState
-import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.inventory.LockReasonCategory
+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.DataStoreSyncState
+import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
+import org.onap.cps.ncmp.rest.model.DataOperationRequest
+import org.onap.cps.ncmp.rest.model.DataOperationDefinition
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
-import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory
import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
+import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper
import org.onap.cps.ncmp.rest.util.DeprecationHelper
import org.onap.cps.spi.FetchDescendantsOption
import org.onap.cps.spi.model.ModuleDefinition
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
-import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores
-import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE
-import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.OPERATIONAL
-
@WebMvcTest(NetworkCmProxyController)
class NetworkCmProxyControllerSpec extends Specification {
- private static final int TIMEOUT_IN_MS = 2000
- private static final boolean NOTIFICATION_ENABLED = true
-
@Autowired
MockMvc mvc
CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
@SpringBean
- CpsNcmpTaskExecutor spiedCpsTaskExecutor = Spy()
+ DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
+
+ @SpringBean
+ CpsNcmpTaskExecutor mockCpsTaskExecutor = Mock()
@SpringBean
DeprecationHelper stubbedDeprecationHelper = Stub()
@SpringBean
- NcmpDatastoreResourceRequestHandlerFactory stubbedNcmpDatastoreResourceRequestHandlerFactory = Stub()
+ NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService, mockNetworkCmProxyQueryService)
+
+ @SpringBean
+ NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService)
@Value('${rest.api.ncmp-base-path}/v1')
def ncmpBasePathV1
def requestBody = '{"some-key":"some-value"}'
- def bulkRequestBody = '["testCmHandle"]'
+
+ def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
@Shared
def NO_TOPIC = null
def NO_REQUEST_ID = null
+ def TIMOUT_FOR_TEST = 1234
- def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
- .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
-
- void setup() {
- stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
- OPERATIONAL) >> getNcmpDatastoreRequestHandler(OPERATIONAL,new NcmpCachedResourceRequestHandler(mockNetworkCmProxyQueryService))
-
- stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
- PASSTHROUGH_OPERATIONAL) >> getNcmpDatastoreRequestHandler(PASSTHROUGH_OPERATIONAL,new NcmpPassthroughResourceRequestHandler())
-
- stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
- PASSTHROUGH_RUNNING) >> getNcmpDatastoreRequestHandler(PASSTHROUGH_RUNNING,new NcmpPassthroughResourceRequestHandler())
-
+ def setup() {
+ ncmpCachedResourceRequestHandler.notificationFeatureEnabled = true
+ ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
+ ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = true
+ ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
}
def 'Get Resource Data from pass-through operational.'() {
response.status == HttpStatus.OK.value()
}
- def 'Get Resource Data from #datastoreInUrl with #scenario.'() {
+ def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
+ def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational" +
+ "?resourceIdentifier=parent/child${additionalUrlParam}"
when: 'get data resource request is performed'
def response = mvc.perform(
get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
then: 'task executor is called appropriate number of times'
- expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, TIMEOUT_IN_MS)
- and: 'response status is expected'
+ 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle('ncmp-datastore:operational', 'h123', 'parent/child', expectedIncludeDescendants)
+ and: 'response status is OK'
response.status == HttpStatus.OK.value()
where: 'the following parameters are used'
- scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions
- 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1
- 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0
- 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1
- 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1
- 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0
- 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1
- }
-
- def 'Fail to get Resource Data from #datastoreInUrl when #scenario.'() {
- given: 'resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
- when: 'get data resource request is performed'
- def response = mvc.perform(
- get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
- then: 'abad request is returned'
- response.status == HttpStatus.BAD_REQUEST.value()
- where: 'the following parameters are used'
- scenario | datastoreInUrl | topicQueryParam
- 'empty topic in url' | 'passthrough-operational' | '&topic=\"\"'
- 'missing topic in url' | 'passthrough-operational' | '&topic='
- 'blank topic value in url' | 'passthrough-operational' | '&topic=\" \"'
- 'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#'
- 'empty topic in url' | 'passthrough-running' | '&topic=\"\"'
- 'missing topic in url' | 'passthrough-running' | '&topic='
- 'blank topic value in url' | 'passthrough-running' | '&topic=\" \"'
- 'invalid non-empty topic value in url' | 'passthrough-running' | '&topic=1_5_*_#'
- }
-
- def 'Get bulk resource data for #datastoreName from dmi service.'() {
- given: 'bulk resource data url'
- def getUrl = "$ncmpBasePathV1/batch/data/ds/${datastoreName}" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
- when: 'post data resource request is performed'
+ scenario | additionalUrlParam || expectedIncludeDescendants
+ 'no additional param' | '' || OMIT_DESCENDANTS
+ 'include descendants true' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
+ 'include descendants TRUE' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
+ 'include descendants false' | '&include-descendants=false' || OMIT_DESCENDANTS
+ 'include descendants FALSE' | '&include-descendants=FALSE' || OMIT_DESCENDANTS
+ 'options (ignored)' | '&options=(a-=1)' || OMIT_DESCENDANTS
+ }
+
+ def 'Execute (async) data operation to read data from dmi service.'() {
+ given: 'data operation url'
+ def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest("read", datastore.datastoreName))
+ when: 'post data operation request is performed'
def response = mvc.perform(
post(getUrl)
.contentType(MediaType.APPLICATION_JSON)
- .content(bulkRequestBody)
+ .content(dataOperationRequestJsonData)
).andReturn().response
then: 'response status is Ok'
response.status == HttpStatus.OK.value()
- // TODO Need to be un-commented as it's failing into onap CICD pipeline
- // but passed into nordix and local build.
- //and: 'the NCMP data service is called with getResourceDataForCmHandleBatch'
- // 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'],
- // 'parent/child',
- // '(a=1,b=2)',
- // 'myTopic',
- // _)
and: 'async request id is generated'
- assert response.contentAsString.contains("requestId")
+ assert response.contentAsString.contains('requestId')
+ then: 'the request is handled asynchronously'
+ 1 * mockCpsTaskExecutor.executeTask(*_)
where: 'the following data stores are used'
- datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
+ datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
+ }
+
+ def 'Execute (async) data operation with some validation error.'() {
+ given: 'data operation url'
+ def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
+ getDataOperationRequest('read', 'invalid datastore'))
+ when: 'post data resource request is performed'
+ def response = mvc.perform(
+ post(getUrl)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(dataOperationRequestJsonData)
+ ).andReturn().response
+ then: 'response status is BAD_REQUEST'
+ response.status == HttpStatus.BAD_REQUEST.value()
}
- def 'Get bulk resource data for non-supported #datastoreName from dmi service.'() {
- given: 'bulk resource data url'
- def getUrl = "$ncmpBasePathV1/batch/data/ds/ncmp-datastore:operational" +
- "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
+ def 'Get data operation resource data when notification feature is disabled for datastore: #datastore.'() {
+ given: 'data operation url'
+ def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+ def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
+ getDataOperationRequest("read", PASSTHROUGH_RUNNING.datastoreName))
+ ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false
when: 'post data resource request is performed'
def response = mvc.perform(
post(getUrl)
.contentType(MediaType.APPLICATION_JSON)
- .content(bulkRequestBody)
+ .content(dataOperationRequestJsonData)
).andReturn().response
- then: 'response status code is 501 not implemented'
- response.status == HttpStatus.NOT_IMPLEMENTED.value()
- where: 'the following data store is un-supported'
- datastoreName << [OPERATIONAL.datastoreName]
+ then: 'response status is Ok'
+ response.status == HttpStatus.OK.value()
+ and: 'async request id is unavailable'
+ assert response.contentAsString == '{"status":"Asynchronous request is unavailable as notification feature is currently disabled."}'
}
def 'Query Resource Data from operational.'() {
response.status == HttpStatus.OK.value()
}
- def 'Query Resource Data using datastore of #datastore'() {
+ def 'Query Resource Data with unsupported datastore'() {
given: 'the query resource data url'
- def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastore}/query" +
+ def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query" +
"?cps-path=/cps/path"
when: 'the query data resource request is performed'
def response = mvc.perform(
).andReturn().response
then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
response.status == 400
- and: 'the error message is that datastore #datastore is not supported'
- response.contentAsString.contains("ncmp-datastore:${datastore} is not supported")
- where: 'the following datastore is used'
- datastore << ["passthrough-running", "passthrough-operational"]
+ and: 'the error message is that the datastore is not supported'
+ response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
}
def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
def 'Get Resource Data from operational with or without descendants'() {
given: 'resource data url with descendants #enabled'
def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
- "?resourceIdentifier=parent/child&include-descendants=${enabled}"
+ "?resourceIdentifier=parent/child&include-descendants=${booleanValue}"
when: 'get data resource request is performed'
def response = mvc.perform(
get(getUrl)
and: 'response status is Ok'
response.status == HttpStatus.OK.value()
where: 'the following parameters are used'
- enabled | descendantsOption
- false | FetchDescendantsOption.OMIT_DESCENDANTS
- true | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+ booleanValue | descendantsOption
+ false | OMIT_DESCENDANTS
+ true | INCLUDE_ALL_DESCENDANTS
}
def 'Attempt execute #operation rest operation on resource data with #scenario'() {
def compositeStateTestObject() {
new CompositeState(cmHandleState: CmHandleState.ADVISED,
- lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
+ lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
lastUpdateTime: formattedDateAndTime.toString(),
dataSyncEnabled: false,
dataStores: dataStores())
def expectedContent = [
'"state":',
'"cmHandleState":"ADVISED"',
- '"reason":"LOCKED_MISBEHAVING"',
- '"details":"lock details"',
+ '"lockReason":{"reason":"MODULE_SYNC_FAILED","details":"lock details"}',
'"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
'"dataSyncEnabled":false',
'"dataSyncState":',
return assertContainsAll(response, expectedContent)
}
- def getNcmpDatastoreRequestHandler(dataStoreType, ncmpDatastoreRequestHandler) {
- if (ncmpDatastoreRequestHandler instanceof NcmpCachedResourceRequestHandler) {
- NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = (NcmpCachedResourceRequestHandler) ncmpDatastoreRequestHandler
- ncmpCachedResourceRequestHandler.dataStoreName = dataStoreType.datastoreName
- } else {
- NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = (NcmpPassthroughResourceRequestHandler) ncmpDatastoreRequestHandler
- ncmpPassthroughResourceRequestHandler.dataStoreName = dataStoreType.datastoreName
- }
- ncmpDatastoreRequestHandler.networkCmProxyDataService = mockNetworkCmProxyDataService
- ncmpDatastoreRequestHandler.cpsNcmpTaskExecutor = spiedCpsTaskExecutor
- ncmpDatastoreRequestHandler.notificationFeatureEnabled = NOTIFICATION_ENABLED
- ncmpDatastoreRequestHandler.timeOutInMilliSeconds = TIMEOUT_IN_MS
- return ncmpDatastoreRequestHandler
+ def getDataOperationRequest(operation, datastore) {
+ def dataOperationRequest = new DataOperationRequest()
+ def dataOperationDefinitions = new ArrayList()
+ dataOperationDefinitions.add(getDataOperationDefinition(operation, datastore))
+ dataOperationRequest.addOperationsItem(dataOperationDefinitions)
+ return dataOperationRequest
}
+
+ def getDataOperationDefinition(operation, datastore) {
+ def dataOperationDefinition = new DataOperationDefinition()
+ dataOperationDefinition.setOperation(operation)
+ dataOperationDefinition.setOperationId("operational-12")
+ dataOperationDefinition.setDatastore(datastore)
+ dataOperationDefinition.setOptions("some option")
+ dataOperationDefinition.setResourceIdentifier("some resource identifier")
+ dataOperationDefinition.addTargetIdsItem("some-cm-handle")
+ return dataOperationDefinition
+ }
+
}